From 74c8b028d42a8c5b080bb861e427f38cedd4ad7c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 15 Dec 2023 18:26:14 +0100 Subject: SL-20743 Use LLMutex in LLImageBase for internal data thread-safety --- indra/llcommon/llapr.cpp | 2 +- indra/llcommon/llapr.h | 2 +- indra/llcommon/llmutex.cpp | 186 +++++++++++++++++++++++++++++++++++++++++++-- indra/llcommon/llmutex.h | 79 ++++++++++++++++++- 4 files changed, 256 insertions(+), 13 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 575c524219..834b0e7a3a 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -571,7 +571,7 @@ S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nb } //static -S32 LLAPRFile::writeEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) +S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) { LL_PROFILE_ZONE_SCOPED; apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 565d7cfb63..f5717ea75e 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -193,7 +193,7 @@ public: // Returns bytes read/written, 0 if read/write fails: static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); - static S32 writeEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append + static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append //******************************************************************************************************************************* }; diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 0273dd5970..f56c2d08a6 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -29,19 +29,20 @@ #include "llthread.h" #include "lltimer.h" -//============================================================================ +//--------------------------------------------------------------------- +// +// LLMutex +// LLMutex::LLMutex() : mCount(0) { } - LLMutex::~LLMutex() { } - void LLMutex::lock() { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD @@ -112,7 +113,7 @@ LLThread::id_t LLMutex::lockingThread() const bool LLMutex::trylock() { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(isSelfLocked()) + if (isSelfLocked()) { //redundant lock mCount++; return true; @@ -135,19 +136,185 @@ bool LLMutex::trylock() return true; } -//============================================================================ +//--------------------------------------------------------------------- +// +// LLSharedMutex +// +LLSharedMutex::LLSharedMutex() +: mLockingThreads(2) // Reserve 2 slots in the map hash table +, mIsShared(false) +{ +} + +bool LLSharedMutex::isLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::lock_guard lock(mLockMutex); + + return !mLockingThreads.empty(); +} + +bool LLSharedMutex::isThreadLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + const_iterator it = mLockingThreads.find(current_thread); + return it != mLockingThreads.end(); +} + +void LLSharedMutex::lockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + // Acquire the mutex immediately if the mutex is not locked exclusively + // or enter a locking state if the mutex is already locked exclusively + mLockMutex.unlock(); + mSharedMutex.lock_shared(); + mLockMutex.lock(); + // Continue after acquiring the mutex + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + mLockMutex.unlock(); +} + +void LLSharedMutex::lockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) + { + mLockingThreads.begin()->second++; + } + else + { + // Acquire the mutex immediately if mLockingThreads is empty + // or enter a locking state if mLockingThreads is not empty + mLockMutex.unlock(); + mSharedMutex.lock(); + mLockMutex.lock(); + // Continue after acquiring the mutex (and possible quitting the locking state) + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + mLockMutex.unlock(); +} + +bool LLSharedMutex::trylockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + if (!mSharedMutex.try_lock_shared()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + + return true; +} + +bool LLSharedMutex::trylockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) + { + mLockingThreads.begin()->second++; + } + else + { + if (!mSharedMutex.try_lock()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + + return true; +} + +void LLSharedMutex::unlockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock_shared(); + } + } +} + +void LLSharedMutex::unlockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock(); + } + } +} + + +//--------------------------------------------------------------------- +// +// LLCondition +// LLCondition::LLCondition() : LLMutex() { } - LLCondition::~LLCondition() { } - void LLCondition::wait() { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD @@ -168,7 +335,10 @@ void LLCondition::broadcast() } - +//--------------------------------------------------------------------- +// +// LLMutexTrylock +// LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) : mMutex(mutex), mLocked(false) diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 0d70da6178..077f810d61 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -32,6 +32,8 @@ #include #include "mutex.h" +#include +#include #include //============================================================================ @@ -66,6 +68,45 @@ protected: #endif }; +//============================================================================ + +class LL_COMMON_API LLSharedMutex +{ +public: + LLSharedMutex(); + + bool isLocked() const; + bool isThreadLocked() const; + bool isShared() const { return mIsShared; } + + void lockShared(); + void lockExclusive(); + template void lock(); + template<> void lock() { lockShared(); } + template<> void lock() { lockExclusive(); } + + bool trylockShared(); + bool trylockExclusive(); + template bool trylock(); + template<> bool trylock() { return trylockShared(); } + template<> bool trylock() { return trylockExclusive(); } + + void unlockShared(); + void unlockExclusive(); + template void unlock(); + template<> void unlock() { unlockShared(); } + template<> void unlock() { unlockExclusive(); } + +private: + std::shared_mutex mSharedMutex; + mutable std::mutex mLockMutex; + std::unordered_map mLockingThreads; + bool mIsShared; + + using iterator = std::unordered_map::iterator; + using const_iterator = std::unordered_map::const_iterator; +}; + // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). class LL_COMMON_API LLCondition : public LLMutex { @@ -81,27 +122,57 @@ protected: std::condition_variable mCond; }; +//============================================================================ + class LLMutexLock { public: LLMutexLock(LLMutex* mutex) { mMutex = mutex; - - if(mMutex) + + if (mMutex) mMutex->lock(); } + ~LLMutexLock() { - if(mMutex) + if (mMutex) mMutex->unlock(); } + private: LLMutex* mMutex; }; //============================================================================ +template +class LLSharedMutexLockTemplate +{ +public: + LLSharedMutexLockTemplate(LLSharedMutex* mutex) + : mSharedMutex(mutex) + { + if (mSharedMutex) + mSharedMutex->lock(); + } + + ~LLSharedMutexLockTemplate() + { + if (mSharedMutex) + mSharedMutex->unlock(); + } + +private: + LLSharedMutex* mSharedMutex; +}; + +using LLSharedMutexLock = LLSharedMutexLockTemplate; +using LLExclusiveMutexLock = LLSharedMutexLockTemplate; + +//============================================================================ + // Scoped locking class similar in function to LLMutexLock but uses // the trylock() method to conditionally acquire lock without // blocking. Caller resolves the resulting condition by calling @@ -127,6 +198,8 @@ private: bool mLocked; }; +//============================================================================ + /** * @class LLScopedLock * @brief Small class to help lock and unlock mutexes. -- cgit v1.2.3 From e4a1feb83079965fbebd356aa694adf100fb7ee3 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Sun, 7 Jan 2024 14:44:52 +0100 Subject: SL-20743 Use LLMutex in LLImageBase for internal data thread-safety (update) --- indra/llcommon/llmutex.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index f56c2d08a6..973ecbc87b 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -196,9 +196,18 @@ void LLSharedMutex::lockExclusive() LLThread::id_t current_thread = LLThread::currentID(); mLockMutex.lock(); - if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) { - mLockingThreads.begin()->second++; + if (mIsShared) + { + // The mutex is already locked in the current thread + // but this lock is SHARED (not EXCLISIVE) + // We can't lock it again, the lock stays shared + // This can lead to a collision (theoretically) + llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE"); + } + it->second++; } else { -- cgit v1.2.3 From da967da9c7aa0d88f478d476e8bb059ba79ca818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Thu, 11 Jan 2024 11:32:15 +0100 Subject: Rename OS X to macOS, mostly in comments We only support 10.13+ now, and it's been called macOS since 10.12. References in code to older versions are unchanged. --- indra/llcommon/llerror.cpp | 2 +- indra/llcommon/llsys.cpp | 6 +++--- indra/llcommon/lluuid.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 414515854a..19c285ea5a 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -700,7 +700,7 @@ namespace bool shouldLogToStderr() { #if LL_DARWIN - // On Mac OS X, stderr from apps launched from the Finder goes to the + // On macOS, stderr from apps launched from the Finder goes to the // console log. It's generally considered bad form to spam too much // there. That scenario can be detected by noticing that stderr is a // character device (S_IFCHR). diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 938685bae6..d3f99a413d 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -269,9 +269,9 @@ LLOSInfo::LLOSInfo() : #elif LL_DARWIN // Initialize mOSStringSimple to something like: - // "Mac OS X 10.6.7" + // "macOS 10.13.1" { - const char * DARWIN_PRODUCT_NAME = "Mac OS X"; + const char * DARWIN_PRODUCT_NAME = "macOS"; int64_t major_version, minor_version, bugfix_version = 0; @@ -294,7 +294,7 @@ LLOSInfo::LLOSInfo() : } // Initialize mOSString to something like: - // "Mac OS X 10.6.7 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" + // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" struct utsname un; if(uname(&un) != -1) { diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 200add404f..285469f0d4 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -510,7 +510,7 @@ S32 LLUUID::getNodeID(unsigned char* node_id) } #elif LL_DARWIN -// Mac OS X version of the UUID generation code... +// macOS version of the UUID generation code... /* * Get an ethernet hardware address, if we can find it... */ -- cgit v1.2.3 From 4a03aa089009af9130076b438192e338bc202584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Thu, 11 Jan 2024 20:36:17 +0100 Subject: operatingSystemVersion and NSOperatingSystemVersion exis since OS X 10.10, and we require macOS 10.13+ now --- indra/llcommon/llsys_objc.mm | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsys_objc.mm b/indra/llcommon/llsys_objc.mm index 3fd85fb1c9..1393ccea50 100644 --- a/indra/llcommon/llsys_objc.mm +++ b/indra/llcommon/llsys_objc.mm @@ -27,38 +27,12 @@ #import "llsys_objc.h" #import -static auto intAtStringIndex(NSArray *array, int index) -{ - return [(NSString *)[array objectAtIndex:index] integerValue]; -} - bool LLGetDarwinOSInfo(int64_t &major, int64_t &minor, int64_t &patch) { - if (NSAppKitVersionNumber > NSAppKitVersionNumber10_8) - { NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; major = osVersion.majorVersion; minor = osVersion.minorVersion; patch = osVersion.patchVersion; - } - else - { - NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile: - @"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"]; - NSArray* versions = [versionString componentsSeparatedByString:@"."]; - NSUInteger count = [versions count]; - if (count > 0) - { - major = intAtStringIndex(versions, 0); - if (count > 1) - { - minor = intAtStringIndex(versions, 1); - if (count > 2) - { - patch = intAtStringIndex(versions, 2); - } - } - } - } + return true; } -- cgit v1.2.3 From ba4e7b989b6c20a49da0eeb450bd2f945b3eefc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Thu, 8 Feb 2024 02:51:51 +0100 Subject: llcommon: BOOL (int) to real bool/LSTATUS --- indra/llcommon/llapr.cpp | 10 +++--- indra/llcommon/llapr.h | 10 +++--- indra/llcommon/llassettype.cpp | 2 +- indra/llcommon/llcommon.cpp | 6 ++-- indra/llcommon/llcommon.h | 2 +- indra/llcommon/llcrc.cpp | 2 +- indra/llcommon/llcrc.h | 2 +- indra/llcommon/lleventemitter.h | 12 +++---- indra/llcommon/llframetimer.cpp | 14 ++++---- indra/llcommon/llframetimer.h | 10 +++--- indra/llcommon/llkeythrottle.h | 8 ++--- indra/llcommon/llmemory.cpp | 6 ++-- indra/llcommon/llmemory.h | 2 +- indra/llcommon/llmetricperformancetester.cpp | 12 +++---- indra/llcommon/llmetricperformancetester.h | 10 +++--- indra/llcommon/llmortician.cpp | 8 ++--- indra/llcommon/llmortician.h | 10 +++--- indra/llcommon/llmutex.cpp | 14 ++++---- indra/llcommon/llnametable.h | 6 ++-- indra/llcommon/llpriqueuemap.h | 14 ++++---- indra/llcommon/llqueuedthread.cpp | 20 ++++++------ indra/llcommon/llqueuedthread.h | 4 +-- indra/llcommon/llsdutil.cpp | 12 +++---- indra/llcommon/llsdutil.h | 2 +- indra/llcommon/llstacktrace.cpp | 8 ++--- indra/llcommon/llsys.cpp | 16 +++++----- indra/llcommon/llsys.h | 4 +-- indra/llcommon/llthread.cpp | 2 +- indra/llcommon/llthread.h | 2 +- indra/llcommon/lltimer.cpp | 24 +++++++------- indra/llcommon/lltimer.h | 14 ++++---- indra/llcommon/lluri.cpp | 6 ++-- indra/llcommon/lluri.h | 2 +- indra/llcommon/lluuid.cpp | 48 ++++++++++++++-------------- indra/llcommon/lluuid.h | 12 +++---- 35 files changed, 168 insertions(+), 168 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 834b0e7a3a..8b99ee93b9 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -56,7 +56,7 @@ void ll_init_apr() if(!LLAPRFile::sAPRFilePoolp) { - LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(FALSE) ; + LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(false) ; } gAPRInitialized = true; @@ -91,7 +91,7 @@ void ll_cleanup_apr() // //LLAPRPool // -LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, BOOL releasePoolFlag) +LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) : mParent(parent), mReleasePoolFlag(releasePoolFlag), mMaxSize(size), @@ -145,7 +145,7 @@ apr_pool_t* LLAPRPool::getAPRPool() return mPool ; } -LLVolatileAPRPool::LLVolatileAPRPool(BOOL is_local, apr_pool_t *parent, apr_size_t size, BOOL releasePoolFlag) +LLVolatileAPRPool::LLVolatileAPRPool(bool is_local, apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) : LLAPRPool(parent, size, releasePoolFlag), mNumActiveRef(0), mNumTotalRef(0) @@ -219,7 +219,7 @@ void LLVolatileAPRPool::clearVolatileAPRPool() llassert(mNumTotalRef <= (FULL_VOLATILE_APR_POOL << 2)) ; } -BOOL LLVolatileAPRPool::isFull() +bool LLVolatileAPRPool::isFull() { return mNumTotalRef > FULL_VOLATILE_APR_POOL ; } @@ -385,7 +385,7 @@ apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, LLV } //use gAPRPoolp. -apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, BOOL use_global_pool) +apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, bool use_global_pool) { apr_status_t s; diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index f5717ea75e..acc4003d27 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -78,7 +78,7 @@ bool LL_COMMON_API ll_apr_is_initialized(); class LL_COMMON_API LLAPRPool { public: - LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE) ; + LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ; virtual ~LLAPRPool() ; virtual apr_pool_t* getAPRPool() ; @@ -93,7 +93,7 @@ protected: apr_pool_t* mParent ; //parent pool apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work. apr_status_t mStatus ; //status when creating the pool - BOOL mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. + bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. }; // @@ -104,14 +104,14 @@ protected: class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool { public: - LLVolatileAPRPool(BOOL is_local = TRUE, apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE); + LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true); virtual ~LLVolatileAPRPool(); /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). apr_pool_t* getVolatileAPRPool() ; void clearVolatileAPRPool() ; - BOOL isFull() ; + bool isFull() ; private: S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool. @@ -158,7 +158,7 @@ public: ~LLAPRFile() ; apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL); - apr_status_t open(const std::string& filename, apr_int32_t flags, BOOL use_global_pool); //use gAPRPoolp. + apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp. apr_status_t close() ; // Returns actual offset, -1 if seek fails diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 6492d888c1..beddfc53cd 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -98,7 +98,7 @@ LLAssetDictionary::LLAssetDictionary() addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); - addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE)); + addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false)); }; diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 6e988260a9..335c586c1d 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -115,7 +115,7 @@ void tracy_aligned_free(void *memblock) #endif //static -BOOL LLCommon::sAprInitialized = FALSE; +bool LLCommon::sAprInitialized = false; static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; @@ -125,7 +125,7 @@ void LLCommon::initClass() if (!sAprInitialized) { ll_init_apr(); - sAprInitialized = TRUE; + sAprInitialized = true; } LLTimer::initClass(); LLThreadSafeRefCount::initThreadSafeRefCount(); @@ -148,6 +148,6 @@ void LLCommon::cleanupClass() if (sAprInitialized) { ll_cleanup_apr(); - sAprInitialized = FALSE; + sAprInitialized = false; } } diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index ca9cad5d05..98c5ed3bc2 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -36,7 +36,7 @@ public: static void initClass(); static void cleanupClass(); private: - static BOOL sAprInitialized; + static bool sAprInitialized; }; #endif diff --git a/indra/llcommon/llcrc.cpp b/indra/llcommon/llcrc.cpp index 626bb1e564..144efa24a8 100644 --- a/indra/llcommon/llcrc.cpp +++ b/indra/llcommon/llcrc.cpp @@ -200,7 +200,7 @@ void LLCRC::update(const std::string& filename) #ifdef _DEBUG -BOOL LLCRC::testHarness() +bool LLCRC::testHarness() { const S32 TEST_BUFFER_SIZE = 16; const char TEST_BUFFER[TEST_BUFFER_SIZE] = "hello &#$)$&Nd0"; /* Flawfinder: ignore */ diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index 3f41b28ffa..322d5c9e27 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -60,7 +60,7 @@ public: #ifdef _DEBUG // This function runs tests to make sure the crc is // working. Returns TRUE if it is. - static BOOL testHarness(); + static bool testHarness(); #endif }; diff --git a/indra/llcommon/lleventemitter.h b/indra/llcommon/lleventemitter.h index cd82fc56f9..3663032b8c 100644 --- a/indra/llcommon/lleventemitter.h +++ b/indra/llcommon/lleventemitter.h @@ -57,14 +57,14 @@ class eventEmitter /////////////////////////////////////////////////////////////////////////////// // - BOOL addObserver ( T* observerIn ) + bool addObserver ( T* observerIn ) { if ( ! observerIn ) - return FALSE; + return false; // check if observer already exists if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) - return FALSE; + return false; // save it observers.push_back ( observerIn ); @@ -74,14 +74,14 @@ class eventEmitter /////////////////////////////////////////////////////////////////////////////// // - BOOL remObserver ( T* observerIn ) + bool remObserver ( T* observerIn ) { if ( ! observerIn ) - return FALSE; + return false; observers.remove ( observerIn ); - return TRUE; + return true; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index 1e9920746b..024ef27228 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -52,12 +52,12 @@ void LLFrameTimer::updateFrameTime() void LLFrameTimer::start() { reset(); - mStarted = TRUE; + mStarted = true; } void LLFrameTimer::stop() { - mStarted = FALSE; + mStarted = false; } void LLFrameTimer::reset() @@ -84,14 +84,14 @@ void LLFrameTimer::pause() { if (mStarted) mStartTime = sFrameTime - mStartTime; // save dtime - mStarted = FALSE; + mStarted = false; } void LLFrameTimer::unpause() { if (!mStarted) mStartTime = sFrameTime - mStartTime; // restore dtime - mStarted = TRUE; + mStarted = true; } void LLFrameTimer::setTimerExpirySec(F32 expiration) @@ -112,7 +112,7 @@ F64 LLFrameTimer::expiresAt() const return expires_at; } -BOOL LLFrameTimer::checkExpirationAndReset(F32 expiration) +bool LLFrameTimer::checkExpirationAndReset(F32 expiration) { //LL_INFOS() << "LLFrameTimer::checkExpirationAndReset()" << LL_ENDL; //LL_INFOS() << " mStartTime:" << mStartTime << LL_ENDL; @@ -123,9 +123,9 @@ BOOL LLFrameTimer::checkExpirationAndReset(F32 expiration) { reset(); setTimerExpirySec(expiration); - return TRUE; + return true; } - return FALSE; + return false; } // static diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 81bd5da8a3..0d90eab2f4 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -39,7 +39,7 @@ class LL_COMMON_API LLFrameTimer { public: - LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} + LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(true) {} // Return the number of seconds since the start of this // application instance. @@ -84,16 +84,16 @@ public: void unpause(); void setTimerExpirySec(F32 expiration); void setExpiryAt(F64 seconds_since_epoch); - BOOL checkExpirationAndReset(F32 expiration); + bool checkExpirationAndReset(F32 expiration); F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; } void setAge(const F64 age) { mStartTime = sFrameTime - age; } // ACCESSORS - BOOL hasExpired() const { return (sFrameTime >= mExpiry); } + bool hasExpired() const { return (sFrameTime >= mExpiry); } F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); } F32 getElapsedTimeF32() const { return mStarted ? (F32)(sFrameTime - mStartTime) : (F32)mStartTime; } - BOOL getStarted() const { return mStarted; } + bool getStarted() const { return mStarted; } // return the seconds since epoch when this timer will expire. F64 expiresAt() const; @@ -142,7 +142,7 @@ protected: // Useful bit of state usually associated with timers, but does // not affect actual functionality - BOOL mStarted; + bool mStarted; }; // Glue code for Havok (or anything else that doesn't want the full .h files) diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index 1f576cc19e..06667c2f86 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -93,7 +93,7 @@ class LLKeyThrottle public: // @param realtime = FALSE for frame-based throttle, TRUE for usec // real-time throttle - LLKeyThrottle(U32 limit, F32 interval, BOOL realtime = TRUE) + LLKeyThrottle(U32 limit, F32 interval, bool realtime = true) : m(* new LLKeyThrottleImpl) { setParameters( limit, interval, realtime ); @@ -287,7 +287,7 @@ public: } // Get the throttling parameters - void getParameters( U32 & out_limit, F32 & out_interval, BOOL & out_realtime ) + void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime ) { out_limit = m.countLimit; out_interval = m.intervalLength; @@ -295,7 +295,7 @@ public: } // Set the throttling behavior - void setParameters( U32 limit, F32 interval, BOOL realtime = TRUE ) + void setParameters( U32 limit, F32 interval, bool realtime = true ) { // limit is the maximum number of keys // allowed per interval (in seconds or frames) @@ -325,7 +325,7 @@ public: protected: LLKeyThrottleImpl& m; - BOOL mIsRealtime; // TRUE to be time based (default), FALSE for frame based + bool mIsRealtime; // TRUE to be time based (default), FALSE for frame based }; #endif diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 7cdf7254ff..196114cee9 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -189,7 +189,7 @@ void* LLMemory::tryToAlloc(void* address, U32 size) } //static -void LLMemory::logMemoryInfo(BOOL update) +void LLMemory::logMemoryInfo(bool update) { LL_PROFILE_ZONE_SCOPED if(update) @@ -343,8 +343,8 @@ void* ll_aligned_malloc_fallback( size_t size, int align ) __asm int 3; } DWORD old; - BOOL Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); - if(FALSE == Res) { + bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); + if(false == Res) { // call debugger __asm int 3; } diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index ac6c969d70..d4d72c243f 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -390,7 +390,7 @@ public: static void* tryToAlloc(void* address, U32 size); static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); static void updateMemoryInfo() ; - static void logMemoryInfo(BOOL update = FALSE); + static void logMemoryInfo(bool update = false); static U32Kilobytes getAvailableMemKB() ; static U32Kilobytes getMaxMemKB() ; diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp index ab509b46eb..3b04947266 100644 --- a/indra/llcommon/llmetricperformancetester.cpp +++ b/indra/llcommon/llmetricperformancetester.cpp @@ -50,18 +50,18 @@ void LLMetricPerformanceTesterBasic::cleanupClass() } /*static*/ -BOOL LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) +bool LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) { llassert_always(tester != NULL); std::string name = tester->getTesterName() ; if (getTester(name)) { LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ; - return FALSE; + return false; } sTesterMap.insert(std::make_pair(name, tester)); - return TRUE; + return true; } /*static*/ @@ -89,7 +89,7 @@ LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::s /*static*/ // Return TRUE if this metric is requested or if the general default "catch all" metric is requested -BOOL LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name) +bool LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name) { return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME))); } @@ -215,8 +215,8 @@ void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* ba resetCurrentCount() ; std::string current_label = getCurrentLabelName(); - BOOL in_base = (*base).has(current_label) ; - BOOL in_current = (*current).has(current_label) ; + bool in_base = (*base).has(current_label) ; + bool in_current = (*current).has(current_label) ; while(in_base || in_current) { diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h index 6561a78f03..678dda7490 100644 --- a/indra/llcommon/llmetricperformancetester.h +++ b/indra/llcommon/llmetricperformancetester.h @@ -48,7 +48,7 @@ public: * Need to be tested after creation of a tester instance so to know if the tester is correctly handled. * A tester might not be added to the map if another tester with the same name already exists. */ - BOOL isValid() const { return mValidInstance; } + bool isValid() const { return mValidInstance; } /** * @brief Write a set of test results to the log LLSD. @@ -122,7 +122,7 @@ private: std::string mName ; // Name of this tester instance S32 mCount ; // Current record count - BOOL mValidInstance; // TRUE if the instance is managed by the map + bool mValidInstance; // TRUE if the instance is managed by the map std::vector< std::string > mMetricStrings ; // Metrics strings // Static members managing the collection of testers @@ -147,12 +147,12 @@ public: * @return Returns TRUE if that metric *or* the default catch all metric has been requested to be logged * @param[in] name - Name of the tester queried. */ - static BOOL isMetricLogRequested(std::string name); + static bool isMetricLogRequested(std::string name); /** * @return Returns TRUE if there's a tester defined, FALSE otherwise. */ - static BOOL hasMetricPerformanceTesters() { return !sTesterMap.empty() ;} + static bool hasMetricPerformanceTesters() { return !sTesterMap.empty() ;} /** * @brief Delete all testers and reset the tester map */ @@ -160,7 +160,7 @@ public: private: // Add a tester to the map. Returns false if adding fails. - static BOOL addTester(LLMetricPerformanceTesterBasic* tester) ; + static bool addTester(LLMetricPerformanceTesterBasic* tester) ; }; /** diff --git a/indra/llcommon/llmortician.cpp b/indra/llcommon/llmortician.cpp index b6ad40c2af..b3e03bde3f 100644 --- a/indra/llcommon/llmortician.cpp +++ b/indra/llcommon/llmortician.cpp @@ -30,7 +30,7 @@ std::list LLMortician::sGraveyard; -BOOL LLMortician::sDestroyImmediate = FALSE; +bool LLMortician::sDestroyImmediate = false; LLMortician::~LLMortician() { @@ -88,19 +88,19 @@ void LLMortician::die() if (sDestroyImmediate) { // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes). - mIsDead = TRUE; + mIsDead = true; delete this; return; } else if (!mIsDead) { - mIsDead = TRUE; + mIsDead = true; sGraveyard.push_back(this); } } // static -void LLMortician::setZealous(BOOL b) +void LLMortician::setZealous(bool b) { sDestroyImmediate = b; } diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index f92c5a11db..e772b7d9c5 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -33,21 +33,21 @@ class LL_COMMON_API LLMortician { public: - LLMortician() { mIsDead = FALSE; } + LLMortician() { mIsDead = false; } static auto graveyardCount() { return sGraveyard.size(); }; static size_t logClass(std::stringstream &str); static void updateClass(); virtual ~LLMortician(); void die(); - BOOL isDead() { return mIsDead; } + bool isDead() { return mIsDead; } // sets destroy immediate true - static void setZealous(BOOL b); + static void setZealous(bool b); private: - static BOOL sDestroyImmediate; + static bool sDestroyImmediate; - BOOL mIsDead; + bool mIsDead; static std::list sGraveyard; }; diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 973ecbc87b..1c025c286d 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -57,9 +57,9 @@ void LLMutex::lock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info auto id = LLThread::currentID(); - if (mIsLocked[id] != FALSE) + if (mIsLocked[id] != false) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = TRUE; + mIsLocked[id] = true; #endif mLockingThread = LLThread::currentID(); @@ -77,9 +77,9 @@ void LLMutex::unlock() #if MUTEX_DEBUG // Access the debug info while we have the lock auto id = LLThread::currentID(); - if (mIsLocked[id] != TRUE) - LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = FALSE; + if (mIsLocked[id] != true) + LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = false; #endif mLockingThread = LLThread::id_t(); @@ -127,9 +127,9 @@ bool LLMutex::trylock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info auto id = LLThread::currentID(); - if (mIsLocked[id] != FALSE) + if (mIsLocked[id] != false) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = TRUE; + mIsLocked[id] = true; #endif mLockingThread = LLThread::currentID(); diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h index 2c8e71263e..4f11c595ed 100644 --- a/indra/llcommon/llnametable.h +++ b/indra/llcommon/llnametable.h @@ -55,16 +55,16 @@ public: mNameMap[tablename] = data; } - BOOL checkName(const std::string& name) const + bool checkName(const std::string& name) const { return checkName(name.c_str()); } // "logically const" even though it modifies the global nametable - BOOL checkName(const char *name) const + bool checkName(const char *name) const { char *tablename = gStringTable.addString(name); - return mNameMap.count(tablename) ? TRUE : FALSE; + return mNameMap.count(tablename) ? true : false; } DATA resolveName(const std::string& name) const diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h index 030e2e0f21..7dd43d87ca 100644 --- a/indra/llcommon/llpriqueuemap.h +++ b/indra/llcommon/llpriqueuemap.h @@ -47,17 +47,17 @@ public: { if (mPriority > b.mPriority) { - return TRUE; + return true; } if (mPriority < b.mPriority) { - return FALSE; + return false; } if (mData > b.mData) { - return TRUE; + return true; } - return FALSE; + return false; } F32 mPriority; @@ -90,18 +90,18 @@ public: mMap.insert(pqm_pair(LLPQMKey(priority, data), data)); } - BOOL pop(DATA_TYPE *datap) + bool pop(DATA_TYPE *datap) { pqm_iter iter; iter = mMap.begin(); if (iter == mMap.end()) { - return FALSE; + return false; } *datap = (*(iter)).second; mMap.erase(iter); - return TRUE; + return true; } void reprioritize(const F32 new_priority, DATA_TYPE data) diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 394212ee0d..38a54337a1 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -37,9 +37,9 @@ // MAIN THREAD LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : LLThread(name), - mIdleThread(TRUE), + mIdleThread(true), mNextHandle(0), - mStarted(FALSE), + mStarted(false), mThreaded(threaded), mRequestQueue(name, 1024 * 1024) { @@ -131,7 +131,7 @@ size_t LLQueuedThread::update(F32 max_time_ms) if (!mThreaded) { startThread(); - mStarted = TRUE; + mStarted = true; } } return updateQueue(max_time_ms); @@ -149,9 +149,9 @@ size_t LLQueuedThread::updateQueue(F32 max_time_ms) mRequestQueue.post([=]() { LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); - mIdleThread = FALSE; + mIdleThread = false; threadedUpdate(); - mIdleThread = TRUE; + mIdleThread = true; } ); } @@ -392,7 +392,7 @@ void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - mIdleThread = FALSE; + mIdleThread = false; //threadedUpdate(); // Get next request from pool @@ -494,7 +494,7 @@ void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) } } - mIdleThread = TRUE; + mIdleThread = true; } // virtual @@ -513,7 +513,7 @@ void LLQueuedThread::run() // call checPause() immediately so we don't try to do anything before the class is fully constructed checkPause(); startThread(); - mStarted = TRUE; + mStarted = true; /*while (1) @@ -522,7 +522,7 @@ void LLQueuedThread::run() // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. checkPause(); - mIdleThread = FALSE; + mIdleThread = false; threadedUpdate(); @@ -531,7 +531,7 @@ void LLQueuedThread::run() if (pending_work == 0) { //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); - mIdleThread = TRUE; + mIdleThread = true; //ms_sleep(1); } //LLThread::yield(); // thread should yield after each request diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 814dbc4c38..eac4a3e2cb 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -159,8 +159,8 @@ public: bool check(); protected: - BOOL mThreaded; // if false, run on main thread and do updates during update() - BOOL mStarted; // required when mThreaded is false to call startThread() from update() + bool mThreaded; // if false, run on main thread and do updates during update() + bool mStarted; // required when mThreaded is false to call startThread() from update() LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle //typedef std::set request_queue_t; diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index e98fc0285a..f31b9a1d14 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -209,7 +209,7 @@ std::string ll_stream_notation_sd(const LLSD& sd) //are not of the same type, false is returned or if the LLSDs are not //of the same value. Ordering of arrays matters //Otherwise, returns true -BOOL compare_llsd_with_template( +bool compare_llsd_with_template( const LLSD& llsd_to_test, const LLSD& template_llsd, LLSD& resultant_llsd) @@ -221,12 +221,12 @@ BOOL compare_llsd_with_template( template_llsd.isDefined() ) { resultant_llsd = template_llsd; - return TRUE; + return true; } else if ( llsd_to_test.type() != template_llsd.type() ) { resultant_llsd = LLSD(); - return FALSE; + return false; } if ( llsd_to_test.isArray() ) @@ -255,7 +255,7 @@ BOOL compare_llsd_with_template( data) ) { resultant_llsd = LLSD(); - return FALSE; + return false; } else { @@ -298,7 +298,7 @@ BOOL compare_llsd_with_template( value) ) { resultant_llsd = LLSD(); - return FALSE; + return false; } else { @@ -321,7 +321,7 @@ BOOL compare_llsd_with_template( } - return TRUE; + return true; } // filter_llsd_with_template() is a direct clone (copy-n-paste) of diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index ad54d1b0be..a2b29604b9 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -72,7 +72,7 @@ LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd); //Returns false if the test is of same type but values differ in type //Otherwise, returns true -LL_COMMON_API BOOL compare_llsd_with_template( +LL_COMMON_API bool compare_llsd_with_template( const LLSD& llsd_to_test, const LLSD& template_llsd, LLSD& resultant_llsd); diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 80057bf0f2..285bdb83ec 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -53,8 +53,8 @@ bool ll_get_stack_trace(std::vector& lines) const S32 MAX_STACK_DEPTH = 32; const S32 STRING_NAME_LENGTH = 200; const S32 FRAME_SKIP = 2; - static BOOL symbolsLoaded = false; - static BOOL firstCall = true; + static bool symbolsLoaded = false; + static bool firstCall = true; HANDLE hProc = GetCurrentProcess(); @@ -92,7 +92,7 @@ bool ll_get_stack_trace(std::vector& lines) for(S32 i=0; i < depth; i++) { std::stringstream stack_line; - BOOL ret; + bool ret; DWORD64 addr = (DWORD64)frames[i]; ret = SymGetSymFromAddr64(hProc, addr, 0, pSym); @@ -134,7 +134,7 @@ void ll_get_stack_trace_internal(std::vector& lines) const S32 STRING_NAME_LENGTH = 256; HANDLE process = GetCurrentProcess(); - SymInitialize( process, NULL, TRUE ); + SymInitialize( process, NULL, true ); void *stack[MAX_STACK_DEPTH]; diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index d3f99a413d..8c5f78b3a7 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -214,7 +214,7 @@ LLOSInfo::LLOSInfo() : DWORD cbData(sizeof(DWORD)); DWORD data(0); HKEY key; - BOOL ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); + LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); if (ERROR_SUCCESS == ret_code) { ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast(&data), &cbData); @@ -571,7 +571,7 @@ bool LLOSInfo::is64Bit() return true; #elif defined(_WIN32) // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate - BOOL f64 = FALSE; + bool f64 = false; return IsWow64Process(GetCurrentProcess(), &f64) && f64; #else return false; @@ -1321,11 +1321,11 @@ private: // Need an instance of FrameWatcher before it does any good static FrameWatcher sFrameWatcher; -BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile) +bool gunzip_file(const std::string& srcfile, const std::string& dstfile) { std::string tmpfile; const S32 UNCOMPRESS_BUFFER_SIZE = 32768; - BOOL retval = FALSE; + bool retval = false; gzFile src = NULL; U8 buffer[UNCOMPRESS_BUFFER_SIZE]; LLFILE *dst = NULL; @@ -1353,18 +1353,18 @@ BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile) fclose(dst); dst = NULL; if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = TRUE; + retval = true; err: if (src != NULL) gzclose(src); if (dst != NULL) fclose(dst); return retval; } -BOOL gzip_file(const std::string& srcfile, const std::string& dstfile) +bool gzip_file(const std::string& srcfile, const std::string& dstfile) { const S32 COMPRESS_BUFFER_SIZE = 32768; std::string tmpfile; - BOOL retval = FALSE; + bool retval = false; U8 buffer[COMPRESS_BUFFER_SIZE]; gzFile dst = NULL; LLFILE *src = NULL; @@ -1404,7 +1404,7 @@ BOOL gzip_file(const std::string& srcfile, const std::string& dstfile) LLFile::remove(dstfile); #endif if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = TRUE; + retval = true; err: if (src != NULL) fclose(src); if (dst != NULL) gzclose(dst); diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 08d4abffa2..ef94a283c8 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -165,9 +165,9 @@ LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); // gunzip srcfile into dstfile. Returns FALSE on error. -BOOL LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); +bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); // gzip srcfile into dstfile. Returns FALSE on error. -BOOL LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); +bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); extern LL_COMMON_API LLCPUInfo gSysCPU; diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 4eaa05c335..b2142c6361 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -188,7 +188,7 @@ void LLThread::threadRun() } LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : - mPaused(FALSE), + mPaused(false), mName(name), mThreadp(NULL), mStatus(STOPPED), diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 50202631e7..c18a0363c7 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -68,7 +68,7 @@ public: // Called from MAIN THREAD. void pause(); void unpause(); - bool isPaused() { return isStopped() || mPaused == TRUE; } + bool isPaused() { return isStopped() || mPaused == true; } // Cause the thread to wake up and check its condition void wake(); diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 1a99ac2886..42c4911ff1 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -96,7 +96,7 @@ U32 micro_sleep(U64 us, U32 max_yields) LARGE_INTEGER ft; ft.QuadPart = -static_cast(us * 10); // '-' using relative time - HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); + HANDLE timer = CreateWaitableTimer(NULL, true, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); @@ -324,7 +324,7 @@ LLTimer::LLTimer() get_timer_info().update(); } - mStarted = TRUE; + mStarted = true; reset(); } @@ -441,31 +441,31 @@ F32SecondsImplicit LLTimer::getRemainingTimeF32() const } -BOOL LLTimer::checkExpirationAndReset(F32 expiration) +bool LLTimer::checkExpirationAndReset(F32 expiration) { U64 cur_ticks = get_clock_count(); if (cur_ticks < mExpirationTicks) { - return FALSE; + return false; } mExpirationTicks = cur_ticks + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); - return TRUE; + return true; } -BOOL LLTimer::hasExpired() const +bool LLTimer::hasExpired() const { return (get_clock_count() >= mExpirationTicks) - ? TRUE : FALSE; + ? true : false; } /////////////////////////////////////////////////////////////////////////////// -BOOL LLTimer::knownBadTimer() +bool LLTimer::knownBadTimer() { - BOOL failed = FALSE; + bool failed = false; #if LL_WINDOWS WCHAR bad_pci_list[][10] = {L"1039:0530", @@ -507,7 +507,7 @@ BOOL LLTimer::knownBadTimer() if (!wcscmp(pci_id, bad_pci_list[check])) { // LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; - failed = TRUE; + failed = true; break; } } @@ -533,7 +533,7 @@ time_t time_corrected() // Is the current computer (in its current time zone) // observing daylight savings time? -BOOL is_daylight_savings() +bool is_daylight_savings() { time_t now = time(NULL); @@ -547,7 +547,7 @@ BOOL is_daylight_savings() } -struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time) +struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time) { S32Hours pacific_offset_hours; if (pacific_daylight_time) diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 010f290b24..8fea02eb92 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -87,19 +87,19 @@ public: // MANIPULATORS - void start() { reset(); mStarted = TRUE; } - void stop() { mStarted = FALSE; } + void start() { reset(); mStarted = true; } + void stop() { mStarted = false; } void reset(); // Resets the timer void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time void setTimerExpirySec(F32SecondsImplicit expiration); - BOOL checkExpirationAndReset(F32 expiration); - BOOL hasExpired() const; + bool checkExpirationAndReset(F32 expiration); + bool hasExpired() const; F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset F64SecondsImplicit getElapsedTimeAndResetF64(); F32SecondsImplicit getRemainingTimeF32() const; - static BOOL knownBadTimer(); + static bool knownBadTimer(); // ACCESSORS F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds @@ -171,14 +171,14 @@ extern LL_COMMON_API S32 gUTCOffset; // Is the current computer (in its current time zone) // observing daylight savings time? -LL_COMMON_API BOOL is_daylight_savings(); +LL_COMMON_API bool is_daylight_savings(); // Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time // Usage: // S32 utc_time; // utc_time = time_corrected(); // struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time); LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring); LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring); diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index 4fb92a8f3e..50ff0f9de0 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -330,7 +330,7 @@ LLURI::LLURI(const std::string& escaped_str) } } -static BOOL isDefault(const std::string& scheme, U16 port) +static bool isDefault(const std::string& scheme, U16 port) { if (scheme == "http") return port == 80; @@ -339,7 +339,7 @@ static BOOL isDefault(const std::string& scheme, U16 port) if (scheme == "ftp") return port == 21; - return FALSE; + return false; } void LLURI::parseAuthorityAndPathUsingOpaque() @@ -627,7 +627,7 @@ std::string LLURI::password() const return unescape(pass); } -BOOL LLURI::defaultPort() const +bool LLURI::defaultPort() const { return isDefault(mScheme, hostPort()); } diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index b8fca0ca51..d6b10bd564 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -99,7 +99,7 @@ public: std::string userName() const; std::string password() const; U16 hostPort() const; // ex.: 80, will include implicit port - BOOL defaultPort() const; // true if port is default for scheme + bool defaultPort() const; // true if port is default for scheme const std::string& escapedPath() const { return mEscapedPath; } std::string path() const; // ex.: "/abc/def", includes leading slash LLSD pathArray() const; // above decoded into an array of strings diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 285469f0d4..b670273da3 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -211,20 +211,20 @@ std::string LLUUID::asString() const return str; } -BOOL LLUUID::set(const char* in_string, BOOL emit) +bool LLUUID::set(const char* in_string, bool emit) { return set(ll_safe_string(in_string), emit); } -BOOL LLUUID::set(const std::string& in_string, BOOL emit) +bool LLUUID::set(const std::string& in_string, bool emit) { - BOOL broken_format = FALSE; + bool broken_format = false; // empty strings should make NULL uuid if (in_string.empty()) { setNull(); - return TRUE; + return true; } if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ @@ -237,7 +237,7 @@ BOOL LLUUID::set(const std::string& in_string, BOOL emit) { LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; } - broken_format = TRUE; + broken_format = true; } else { @@ -248,7 +248,7 @@ BOOL LLUUID::set(const std::string& in_string, BOOL emit) LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; } setNull(); - return FALSE; + return false; } } @@ -287,7 +287,7 @@ BOOL LLUUID::set(const std::string& in_string, BOOL emit) LL_WARNS() << "Invalid UUID string character" << LL_ENDL; } setNull(); - return FALSE; + return false; } mData[i] = mData[i] << 4; @@ -312,27 +312,27 @@ BOOL LLUUID::set(const std::string& in_string, BOOL emit) LL_WARNS() << "Invalid UUID string character" << LL_ENDL; } setNull(); - return FALSE; + return false; } cur_pos++; } - return TRUE; + return true; } -BOOL LLUUID::validate(const std::string& in_string) +bool LLUUID::validate(const std::string& in_string) { - BOOL broken_format = FALSE; + bool broken_format = false; if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ { // I'm a moron. First implementation didn't have the right UUID format. if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ { - broken_format = TRUE; + broken_format = true; } else { - return FALSE; + return false; } } @@ -360,7 +360,7 @@ BOOL LLUUID::validate(const std::string& in_string) } else { - return FALSE; + return false; } cur_pos++; @@ -376,11 +376,11 @@ BOOL LLUUID::validate(const std::string& in_string) } else { - return FALSE; + return false; } cur_pos++; } - return TRUE; + return true; } const LLUUID& LLUUID::operator^=(const LLUUID& rhs) @@ -736,12 +736,12 @@ void LLUUID::getCurrentTime(uuid_time_t* timestamp) static uuid_time_t time_last; static U32 uuids_this_tick; - static BOOL init = FALSE; + static bool init = false; if (!init) { getSystemTime(&time_last); uuids_this_tick = uuids_per_tick; - init = TRUE; + init = true; mMutex = new LLMutex(); } @@ -889,11 +889,11 @@ U32 LLUUID::getRandomSeed() return U32(seed64) ^ U32(seed64 >> 32); } -BOOL LLUUID::parseUUID(const std::string& buf, LLUUID* value) +bool LLUUID::parseUUID(const std::string& buf, LLUUID* value) { if (buf.empty() || value == NULL) { - return FALSE; + return false; } std::string temp(buf); @@ -901,9 +901,9 @@ BOOL LLUUID::parseUUID(const std::string& buf, LLUUID* value) if (LLUUID::validate(temp)) { value->set(temp); - return TRUE; + return true; } - return FALSE; + return false; } //static @@ -989,7 +989,7 @@ bool LLUUID::operator!=(const LLUUID& rhs) const } */ -BOOL LLUUID::notNull() const +bool LLUUID::notNull() const { U32* word = (U32*)mData; return (word[0] | word[1] | word[2] | word[3]) > 0; @@ -997,7 +997,7 @@ BOOL LLUUID::notNull() const // Faster than == LLUUID::null because doesn't require // as much memory access. -BOOL LLUUID::isNull() const +bool LLUUID::isNull() const { U32* word = (U32*)mData; // If all bits are zero, return !0 == TRUE diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 80597fa186..89c512ecf7 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -65,8 +65,8 @@ public: static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc. - BOOL set(const char *in_string, BOOL emit = TRUE); // Convert from string, if emit is FALSE, do not emit warnings - BOOL set(const std::string& in_string, BOOL emit = TRUE); // Convert from string, if emit is FALSE, do not emit warnings + bool set(const char *in_string, bool emit = true); // Convert from string, if emit is FALSE, do not emit warnings + bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is FALSE, do not emit warnings void setNull(); // Faster than setting to LLUUID::null. S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2); @@ -76,8 +76,8 @@ public: // // ACCESSORS // - BOOL isNull() const; // Faster than comparing to LLUUID::null. - BOOL notNull() const; // Faster than comparing to LLUUID::null. + bool isNull() const; // Faster than comparing to LLUUID::null. + bool notNull() const; // Faster than comparing to LLUUID::null. // JC: This is dangerous. It allows UUIDs to be cast automatically // to integers, among other things. Use isNull() or notNull(). // operator bool() const; @@ -124,7 +124,7 @@ public: return tmp[0] ^ tmp[1]; } - static BOOL validate(const std::string& in_string); // Validate that the UUID string is legal. + static bool validate(const std::string& in_string); // Validate that the UUID string is legal. static const LLUUID null; static LLMutex * mMutex; @@ -132,7 +132,7 @@ public: static U32 getRandomSeed(); static S32 getNodeID(unsigned char * node_id); - static BOOL parseUUID(const std::string& buf, LLUUID* value); + static bool parseUUID(const std::string& buf, LLUUID* value); U8 mData[UUID_BYTES]; }; -- cgit v1.2.3 From 364bd17304dd96ccf62fcee49de1df650b655e74 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Thu, 8 Feb 2024 22:26:52 +0100 Subject: Replace remaining BOOL with bool in llcommon and remove dead code --- indra/llcommon/StackWalker.cpp | 72 ++++++++++++------------ indra/llcommon/StackWalker.h | 6 +- indra/llcommon/llapp.cpp | 124 +++-------------------------------------- indra/llcommon/llapp.h | 6 +- 4 files changed, 50 insertions(+), 158 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index 56defc6465..7808c36757 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -292,10 +292,10 @@ public: free(m_szSymPath); m_szSymPath = NULL; } - BOOL Init(LPCSTR szSymPath) + bool Init(LPCSTR szSymPath) { if (m_parent == NULL) - return FALSE; + return false; // Dynamically load the Entry-Points for dbghelp.dll: // First try to load the newsest one from TCHAR szTemp[4096]; @@ -364,7 +364,7 @@ public: if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one m_hDbhHelp = LoadLibrary( _T("dbghelp.dll") ); if (m_hDbhHelp == NULL) - return FALSE; + return false; pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" ); pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" ); @@ -388,7 +388,7 @@ public: FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; pSC = NULL; - return FALSE; + return false; } // SymInitialize @@ -415,7 +415,7 @@ public: GetUserNameA(szUserName, &dwSize); this->m_parent->OnSymInit(buf, symOptions, szUserName); - return TRUE; + return false; } StackWalker *m_parent; @@ -555,7 +555,7 @@ private: typedef MODULEENTRY32 * LPMODULEENTRY32; #pragma pack( pop ) - BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) + bool GetModuleListTH32(HANDLE hProcess, DWORD pid) { // CreateToolhelp32Snapshot() typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID); @@ -592,13 +592,13 @@ private: } if (hToolhelp == NULL) - return FALSE; + return false; hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); if (hSnap == (HANDLE) -1) { FreeLibrary(hToolhelp); - return FALSE; + return false; } keepGoing = !!pM32F( hSnap, &me ); @@ -612,8 +612,8 @@ private: CloseHandle(hSnap); FreeLibrary(hToolhelp); if (cnt <= 0) - return FALSE; - return TRUE; + return false; + return true; } // GetModuleListTH32 // **************************************** PSAPI ************************ @@ -623,7 +623,7 @@ private: LPVOID EntryPoint; } MODULEINFO, *LPMODULEINFO; - BOOL GetModuleListPSAPI(HANDLE hProcess) + bool GetModuleListPSAPI(HANDLE hProcess) { // EnumProcessModules() typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); @@ -652,7 +652,7 @@ private: hPsapi = LoadLibrary( _T("psapi.dll") ); if (hPsapi == NULL) - return FALSE; + return false; pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" ); pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" ); @@ -662,7 +662,7 @@ private: { // we couldn't find all functions FreeLibrary(hPsapi); - return FALSE; + return false; } hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE))); @@ -797,7 +797,7 @@ private: return result; } public: - BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) + bool LoadModules(HANDLE hProcess, DWORD dwProcessId) { // first try toolhelp32 if (GetModuleListTH32(hProcess, dwProcessId)) @@ -807,13 +807,13 @@ public: } - BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) + bool GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) { memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3)); if(this->pSGMI == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } // First try to use the larger ModuleInfo-Structure pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); @@ -821,7 +821,7 @@ public: if (pData == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; + return false; } memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3)); static bool s_useV3Version = true; @@ -833,7 +833,7 @@ public: memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); free(pData); - return TRUE; + return true; } s_useV3Version = false; // to prevent unneccessarry calls with the larger struct... } @@ -847,11 +847,11 @@ public: memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); free(pData); - return TRUE; + return true; } free(pData); SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } }; @@ -860,7 +860,7 @@ StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) { this->m_verbose = true; this->m_options = OptionsAll; - this->m_modulesLoaded = FALSE; + this->m_modulesLoaded = false; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; @@ -871,7 +871,7 @@ StackWalker::StackWalker(bool verbose, int options, LPCSTR szSymPath, DWORD dwPr { this->m_verbose = verbose; this->m_options = options; - this->m_modulesLoaded = FALSE; + this->m_modulesLoaded = false; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; @@ -895,15 +895,15 @@ StackWalker::~StackWalker() this->m_sw = NULL; } -BOOL StackWalker::LoadModules() +bool StackWalker::LoadModules() { if (this->m_sw == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } if (m_modulesLoaded != FALSE) - return TRUE; + return true; // Build the sym-path: char *szSymPath = NULL; @@ -914,7 +914,7 @@ BOOL StackWalker::LoadModules() if (szSymPath == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; + return false; } szSymPath[0] = 0; // Now first add the (optional) provided sympath: @@ -994,18 +994,18 @@ BOOL StackWalker::LoadModules() } // if SymBuildPath // First Init the whole stuff... - BOOL bRet = this->m_sw->Init(szSymPath); + bool bRet = this->m_sw->Init(szSymPath); if (szSymPath != NULL) free(szSymPath); szSymPath = NULL; - if (bRet == FALSE) + if (!bRet) { this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); - if (bRet != FALSE) - m_modulesLoaded = TRUE; + if (bRet) + m_modulesLoaded = true; return bRet; } @@ -1017,7 +1017,7 @@ BOOL StackWalker::LoadModules() static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; static LPVOID s_readMemoryFunction_UserData = NULL; -BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) +bool StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) { m_verbose = verbose; CONTEXT c; @@ -1029,13 +1029,13 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con bool bLastEntryCalled = true; int curRecursionCount = 0; - if (m_modulesLoaded == FALSE) + if (!m_modulesLoaded) this->LoadModules(); // ignore the result... if (this->m_sw->m_hDbhHelp == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } s_readMemoryFunction = readMemoryFunction; @@ -1062,7 +1062,7 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con if (GetThreadContext(hThread, &c) == FALSE) { ResumeThread(hThread); - return FALSE; + return false; } } } @@ -1262,7 +1262,7 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con if (context == NULL) ResumeThread(hThread); - return TRUE; + return true; } BOOL __stdcall StackWalker::myReadProcMem( diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h index 4634765d0b..3667f59b38 100644 --- a/indra/llcommon/StackWalker.h +++ b/indra/llcommon/StackWalker.h @@ -112,9 +112,9 @@ public: LPVOID pUserData // optional data, which was passed in "ShowCallstack" ); - BOOL LoadModules(); + bool LoadModules(); - BOOL ShowCallstack( + bool ShowCallstack( bool verbose, HANDLE hThread = GetCurrentThread(), const CONTEXT *context = NULL, @@ -159,7 +159,7 @@ protected: StackWalkerInternal *m_sw; HANDLE m_hProcess; DWORD m_dwProcessId; - BOOL m_modulesLoaded; + bool m_modulesLoaded; LPSTR m_szSymPath; bool m_verbose; diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index b99166991f..0837ece80d 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -52,15 +52,7 @@ // // Signal handling -// -// Windows uses structured exceptions, so it's handled a bit differently. -// -#if LL_WINDOWS -#include "windows.h" - -LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop); -BOOL ConsoleCtrlHandler(DWORD fdwCtrlType); -#else +#ifndef LL_WINDOWS # include # include // for fork() void setup_signals(); @@ -87,24 +79,24 @@ S32 LL_HEARTBEAT_SIGNAL = SIGUSR2; S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1; S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2; # endif // LL_DARWIN -#endif // LL_WINDOWS +#endif // !LL_WINDOWS // the static application instance LLApp* LLApp::sApplication = NULL; // Allows the generation of core files for post mortem under gdb // and disables crashlogger -BOOL LLApp::sDisableCrashlogger = FALSE; +bool LLApp::sDisableCrashlogger = false; // Local flag for whether or not to do logging in signal handlers. //static -BOOL LLApp::sLogInSignal = FALSE; +bool LLApp::sLogInSignal = false; // static // Keeps track of application status LLScalarCond LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; LLAppErrorHandler LLApp::sErrorHandler = NULL; -BOOL LLApp::sErrorThreadRunning = FALSE; +bool LLApp::sErrorThreadRunning = false; LLApp::LLApp() @@ -327,33 +319,6 @@ void LLApp::stepFrame() mRunner.run(); } -#if LL_WINDOWS -//The following code is needed for 32-bit apps on 64-bit windows to keep it from eating -//crashes. It is a lovely undocumented 'feature' in SP1 of Windows 7. An excellent -//in-depth article on the issue may be found here: http://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesn-work/ -void EnableCrashingOnCrashes() -{ - typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags); - typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags); - const DWORD EXCEPTION_SWALLOWING = 0x1; - - HMODULE kernel32 = LoadLibraryA("kernel32.dll"); - tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32, - "GetProcessUserModeExceptionPolicy"); - tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32, - "SetProcessUserModeExceptionPolicy"); - if (pGetPolicy && pSetPolicy) - { - DWORD dwFlags; - if (pGetPolicy(&dwFlags)) - { - // Turn off the filter - pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING); - } - } -} -#endif - void LLApp::setupErrorHandling(bool second_instance) { // Error handling is done by starting up an error handling thread, which just sleeps and @@ -504,13 +469,13 @@ bool LLApp::isExiting() void LLApp::disableCrashlogger() { - sDisableCrashlogger = TRUE; + sDisableCrashlogger = true; } // static bool LLApp::isCrashloggerDisabled() { - return (sDisableCrashlogger == TRUE); + return (sDisableCrashlogger == true); } // static @@ -523,77 +488,7 @@ int LLApp::getPid() #endif } -#if LL_WINDOWS -LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop) -{ - // Translate the signals/exceptions into cross-platform stuff - // Windows implementation - - // Make sure the user sees something to indicate that the app crashed. - LONG retval; - - if (LLApp::isError()) - { - LL_WARNS() << "Got another fatal signal while in the error handler, die now!" << LL_ENDL; - retval = EXCEPTION_EXECUTE_HANDLER; - return retval; - } - - // Flag status to error, so thread_error starts its work - LLApp::setError(); - - // Block in the exception handler until the app has stopped - // This is pretty sketchy, but appears to work just fine - while (!LLApp::isStopped()) - { - ms_sleep(10); - } - - // - // Generate a minidump if we can. - // - // TODO: This needs to be ported over form the viewer-specific - // LLWinDebug class - - // - // At this point, we always want to exit the app. There's no graceful - // recovery for an unhandled exception. - // - // Just kill the process. - retval = EXCEPTION_EXECUTE_HANDLER; - return retval; -} - -// Win32 doesn't support signals. This is used instead. -BOOL ConsoleCtrlHandler(DWORD fdwCtrlType) -{ - switch (fdwCtrlType) - { - case CTRL_BREAK_EVENT: - case CTRL_LOGOFF_EVENT: - case CTRL_SHUTDOWN_EVENT: - case CTRL_CLOSE_EVENT: // From end task or the window close button. - case CTRL_C_EVENT: // from CTRL-C on the keyboard - // Just set our state to quitting, not error - if (LLApp::isQuitting() || LLApp::isError()) - { - // We're already trying to die, just ignore this signal - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; - } - return TRUE; - } - LLApp::setQuitting(); - return TRUE; - - default: - return FALSE; - } -} - -#else //!LL_WINDOWS - +#ifndef LL_WINDOWS void setup_signals() { // @@ -811,9 +706,6 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) } } -#if LL_LINUX -#endif - bool unix_post_minidump_callback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded) diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index c832c8b142..997fc5a951 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -291,8 +291,8 @@ protected: static void setStatus(EAppStatus status); // Use this to change the application status. static LLScalarCond sStatus; // Reflects current application status - static BOOL sErrorThreadRunning; // Set while the error thread is running - static BOOL sDisableCrashlogger; // Let the OS handle crashes for us. + static bool sErrorThreadRunning; // Set while the error thread is running + static bool sDisableCrashlogger; // Let the OS handle crashes for us. std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. std::string mDumpPath; //output path for google breakpad. Dependency workaround. @@ -337,7 +337,7 @@ private: #endif public: - static BOOL sLogInSignal; + static bool sLogInSignal; }; #endif // LL_LLAPP_H -- cgit v1.2.3 From 67876e6cb01871201048955868151971883aeaf4 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 9 Feb 2024 02:18:20 +0200 Subject: Follow up 364bd17: restore return value --- indra/llcommon/StackWalker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index 7808c36757..4ecff4ee69 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -415,7 +415,7 @@ public: GetUserNameA(szUserName, &dwSize); this->m_parent->OnSymInit(buf, symOptions, szUserName); - return false; + return true; } StackWalker *m_parent; -- cgit v1.2.3 From b8952923ac2564f85b83da6893f89a6a45c952fd Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 9 Feb 2024 23:06:55 +0200 Subject: Triage Issue #49 Better inspection of data urls in media --- indra/llcommon/llbase64.cpp | 16 ++++++++++++++++ indra/llcommon/llbase64.h | 1 + 2 files changed, 17 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llbase64.cpp b/indra/llcommon/llbase64.cpp index bb85fe32a3..6591e9b49a 100644 --- a/indra/llcommon/llbase64.cpp +++ b/indra/llcommon/llbase64.cpp @@ -59,3 +59,19 @@ std::string LLBase64::encode(const U8* input, size_t input_size) return output; } +std::string LLBase64::decodeAsString(const std::string &input) +{ + int b64_buffer_length = apr_base64_decode_len(input.c_str()); + char* b64_buffer = new char[b64_buffer_length]; + + // This is faster than apr_base64_encode() if you know + // you're not on an EBCDIC machine. Also, the output is + // null terminated, even though the documentation doesn't + // specify. See apr_base64.c for details. JC + b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str()); + std::string res; + res.assign(b64_buffer); + delete[] b64_buffer; + return res; +} + diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index 16d2c217d0..b985963fc4 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -32,6 +32,7 @@ class LL_COMMON_API LLBase64 { public: static std::string encode(const U8* input, size_t input_size); + static std::string decodeAsString(const std::string& input); }; #endif -- cgit v1.2.3 From 04a02e83e9dcc29d4649e8003d523621b5119d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Fri, 9 Feb 2024 23:22:06 +0100 Subject: misc: BOOL (int) to real bool --- indra/llcommon/llcallbacklist.cpp | 8 ++++---- indra/llcommon/llstring.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 9f23ce5317..4ccf08cbbb 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -70,11 +70,11 @@ bool LLCallbackList::containsFunction( callback_t func, void *data) callback_list_t::iterator iter = find(func,data); if (iter != mCallbackList.end()) { - return TRUE; + return true; } else { - return FALSE; + return false; } } @@ -85,11 +85,11 @@ bool LLCallbackList::deleteFunction( callback_t func, void *data) if (iter != mCallbackList.end()) { mCallbackList.erase(iter); - return TRUE; + return true; } else { - return FALSE; + return false; } } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 1fd6cac14a..66ee42af2a 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1598,12 +1598,12 @@ template BOOL LLStringUtilBase::containsNonprintable(const string_type& string) { const char MIN = 32; - BOOL rv = FALSE; + BOOL rv = false; for (size_type i = 0; i < string.size(); i++) { if(string[i] < MIN) { - rv = TRUE; + rv = true; break; } } -- cgit v1.2.3 From 70f8dc7a4f4be217fea5439e474fc75e567c23c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Sat, 10 Feb 2024 22:37:52 +0100 Subject: miscellaneous: BOOL (int) to real bool --- indra/llcommon/is_approx_equal_fraction.h | 4 +- indra/llcommon/llcallbacklist.cpp | 2 +- indra/llcommon/llmainthreadtask.h | 2 +- indra/llcommon/llstring.h | 118 +++++++++++++++--------------- 4 files changed, 63 insertions(+), 63 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/is_approx_equal_fraction.h b/indra/llcommon/is_approx_equal_fraction.h index 4a9b2e2725..e86d58b70f 100644 --- a/indra/llcommon/is_approx_equal_fraction.h +++ b/indra/llcommon/is_approx_equal_fraction.h @@ -45,7 +45,7 @@ template inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) { - BOOL ret = TRUE; + BOOL ret = true; FTYPE diff = (FTYPE) fabs(x - y); S32 diffInt = (S32) diff; @@ -58,7 +58,7 @@ inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) // based on the number of bits used for packing decimal portion. if (diffInt != 0 || diffFracTolerance > 1) { - ret = FALSE; + ret = false; } return ret; diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 4ccf08cbbb..3b63d8f95a 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -194,7 +194,7 @@ private: BOOL tick() { mCallable(); - return TRUE; + return true; } nullary_func_t mCallable; diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index d509b687c0..878f57cab6 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -85,7 +85,7 @@ private: // obtained by get_future() mTask(); // tell LLEventTimer we're done (one shot) - return TRUE; + return true; } // Given arbitrary CALLABLE, which might be a lambda, how are we // supposed to obtain its signature for std::packaged_task? It seems diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 66ee42af2a..3620a5b211 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -314,7 +314,7 @@ public: static void toLower(string_type& string); // True if this is the head of s. - static BOOL isHead( const string_type& string, const T* s ); + static bool isHead( const string_type& string, const T* s ); /** * @brief Returns true if string starts with substr @@ -356,7 +356,7 @@ public: static void replaceChar( string_type& string, T target, T replacement ); static void replaceString( string_type& string, string_type target, string_type replacement ); - static BOOL containsNonprintable(const string_type& string); + static bool containsNonprintable(const string_type& string); static void stripNonprintable(string_type& string); /** @@ -382,15 +382,15 @@ public: static void _makeASCII(string_type& string); // Conversion to other data types - static BOOL convertToBOOL(const string_type& string, BOOL& value); - static BOOL convertToU8(const string_type& string, U8& value); - static BOOL convertToS8(const string_type& string, S8& value); - static BOOL convertToS16(const string_type& string, S16& value); - static BOOL convertToU16(const string_type& string, U16& value); - static BOOL convertToU32(const string_type& string, U32& value); - static BOOL convertToS32(const string_type& string, S32& value); - static BOOL convertToF32(const string_type& string, F32& value); - static BOOL convertToF64(const string_type& string, F64& value); + static bool convertToBOOL(const string_type& string, BOOL& value); + static bool convertToU8(const string_type& string, U8& value); + static bool convertToS8(const string_type& string, S8& value); + static bool convertToS16(const string_type& string, S16& value); + static bool convertToU16(const string_type& string, U16& value); + static bool convertToU32(const string_type& string, U32& value); + static bool convertToS32(const string_type& string, S32& value); + static bool convertToF32(const string_type& string, F32& value); + static bool convertToF64(const string_type& string, F64& value); ///////////////////////////////////////////////////////////////////////////////////////// // Utility functions for working with char*'s and strings @@ -415,7 +415,7 @@ public: static S32 compareDictInsensitive(const string_type& a, const string_type& b); // Puts compareDict() in a form appropriate for LL container classes to use for sorting. - static BOOL precedesDict( const string_type& a, const string_type& b ); + static bool precedesDict( const string_type& a, const string_type& b ); // A replacement for strncpy. // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds @@ -1362,7 +1362,7 @@ S32 LLStringUtilBase::compareDictInsensitive(const string_type& astr, const s // Puts compareDict() in a form appropriate for LL container classes to use for sorting. // static template -BOOL LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) +bool LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) { if( a.size() && b.size() ) { @@ -1595,10 +1595,10 @@ void LLStringUtilBase::replaceTabsWithSpaces( string_type& str, size_type spa //static template -BOOL LLStringUtilBase::containsNonprintable(const string_type& string) +bool LLStringUtilBase::containsNonprintable(const string_type& string) { const char MIN = 32; - BOOL rv = false; + bool rv = false; for (size_type i = 0; i < string.size(); i++) { if(string[i] < MIN) @@ -1732,12 +1732,12 @@ void LLStringUtilBase::copyInto(string_type& dst, const string_type& src, siz // True if this is the head of s. //static template -BOOL LLStringUtilBase::isHead( const string_type& string, const T* s ) -{ +bool LLStringUtilBase::isHead( const string_type& string, const T* s ) +{ if( string.empty() ) { // Early exit - return FALSE; + return false; } else { @@ -1804,11 +1804,11 @@ auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt } template -BOOL LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) +bool LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) { if( string.empty() ) { - return FALSE; + return false; } string_type temp( string ); @@ -1821,8 +1821,8 @@ BOOL LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) (temp == "true") || (temp == "True") ) { - value = TRUE; - return TRUE; + value = true; + return true; } else if( @@ -1833,71 +1833,71 @@ BOOL LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) (temp == "false") || (temp == "False") ) { - value = FALSE; - return TRUE; + value = false; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToU8(const string_type& string, U8& value) +bool LLStringUtilBase::convertToU8(const string_type& string, U8& value) { S32 value32 = 0; - BOOL success = convertToS32(string, value32); + bool success = convertToS32(string, value32); if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) { value = (U8) value32; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToS8(const string_type& string, S8& value) +bool LLStringUtilBase::convertToS8(const string_type& string, S8& value) { S32 value32 = 0; - BOOL success = convertToS32(string, value32); + bool success = convertToS32(string, value32); if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) { value = (S8) value32; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToS16(const string_type& string, S16& value) +bool LLStringUtilBase::convertToS16(const string_type& string, S16& value) { S32 value32 = 0; - BOOL success = convertToS32(string, value32); + bool success = convertToS32(string, value32); if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) { value = (S16) value32; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToU16(const string_type& string, U16& value) +bool LLStringUtilBase::convertToU16(const string_type& string, U16& value) { S32 value32 = 0; - BOOL success = convertToS32(string, value32); + bool success = convertToS32(string, value32); if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) { value = (U16) value32; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToU32(const string_type& string, U32& value) +bool LLStringUtilBase::convertToU32(const string_type& string, U32& value) { if( string.empty() ) { - return FALSE; + return false; } string_type temp( string ); @@ -1907,17 +1907,17 @@ BOOL LLStringUtilBase::convertToU32(const string_type& string, U32& value) if(i_stream >> v) { value = v; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToS32(const string_type& string, S32& value) +bool LLStringUtilBase::convertToS32(const string_type& string, S32& value) { if( string.empty() ) { - return FALSE; + return false; } string_type temp( string ); @@ -1930,34 +1930,34 @@ BOOL LLStringUtilBase::convertToS32(const string_type& string, S32& value) //if((LONG_MAX == v) || (LONG_MIN == v)) //{ // // Underflow or overflow - // return FALSE; + // return false; //} value = v; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToF32(const string_type& string, F32& value) +bool LLStringUtilBase::convertToF32(const string_type& string, F32& value) { F64 value64 = 0.0; - BOOL success = convertToF64(string, value64); + bool success = convertToF64(string, value64); if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) { value = (F32) value64; - return TRUE; + return true; } - return FALSE; + return false; } template -BOOL LLStringUtilBase::convertToF64(const string_type& string, F64& value) +bool LLStringUtilBase::convertToF64(const string_type& string, F64& value) { if( string.empty() ) { - return FALSE; + return false; } string_type temp( string ); @@ -1970,13 +1970,13 @@ BOOL LLStringUtilBase::convertToF64(const string_type& string, F64& value) //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) //{ // // Underflow or overflow - // return FALSE; + // return false; //} value = v; - return TRUE; + return true; } - return FALSE; + return false; } template -- cgit v1.2.3 From 4419bb870986c6900fc096338622d27b999cd771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Sun, 11 Feb 2024 01:23:28 +0100 Subject: more misc: BOOL (int) to real bool --- indra/llcommon/is_approx_equal_fraction.h | 8 ++++---- indra/llcommon/llcallbacklist.cpp | 4 ++-- indra/llcommon/lleventtimer.h | 4 ++-- indra/llcommon/lllivefile.cpp | 6 +++--- indra/llcommon/llmainthreadtask.h | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/is_approx_equal_fraction.h b/indra/llcommon/is_approx_equal_fraction.h index e86d58b70f..732d168986 100644 --- a/indra/llcommon/is_approx_equal_fraction.h +++ b/indra/llcommon/is_approx_equal_fraction.h @@ -43,9 +43,9 @@ * signatures. */ template -inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) +inline bool is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) { - BOOL ret = true; + bool ret = true; FTYPE diff = (FTYPE) fabs(x - y); S32 diffInt = (S32) diff; @@ -65,13 +65,13 @@ inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) } /// F32 flavor -inline BOOL is_approx_equal_fraction(F32 x, F32 y, U32 frac_bits) +inline bool is_approx_equal_fraction(F32 x, F32 y, U32 frac_bits) { return is_approx_equal_fraction_impl(x, y, frac_bits); } /// F64 flavor -inline BOOL is_approx_equal_fraction(F64 x, F64 y, U32 frac_bits) +inline bool is_approx_equal_fraction(F64 x, F64 y, U32 frac_bits) { return is_approx_equal_fraction_impl(x, y, frac_bits); } diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 3b63d8f95a..a2800d42ce 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -191,7 +191,7 @@ public: } private: - BOOL tick() + bool tick() { mCallable(); return true; @@ -215,7 +215,7 @@ public: { } private: - BOOL tick() + bool tick() { return mCallable(); } diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index dbbfe0c6e6..9bd34915a5 100644 --- a/indra/llcommon/lleventtimer.h +++ b/indra/llcommon/lleventtimer.h @@ -43,7 +43,7 @@ public: //function to be called at the supplied frequency // Normally return FALSE; TRUE will delete the timer after the function returns. - virtual BOOL tick() = 0; + virtual bool tick() = 0; static void updateClass(); @@ -86,7 +86,7 @@ public: mOnce(once), mCallable(callable) {} - BOOL tick() override + bool tick() override { mCallable(); // true tells updateClass() to delete this instance diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index ea485c2d86..852ddb45e9 100644 --- a/indra/llcommon/lllivefile.cpp +++ b/indra/llcommon/lllivefile.cpp @@ -170,10 +170,10 @@ namespace : LLEventTimer(refresh), mLiveFile(f) { } - BOOL tick() - { + bool tick() + { mLiveFile.checkAndReload(); - return FALSE; + return false; } private: diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 878f57cab6..5fae0212c4 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -79,7 +79,7 @@ private: LLEventTimer(0), mTask(std::forward(callable)) {} - BOOL tick() override + bool tick() override { // run the task on the main thread, will populate the future // obtained by get_future() -- cgit v1.2.3 From 9480a98cffaafa5826b8daad20020cf399bbbefc Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 16 Feb 2024 00:07:58 +0100 Subject: Replace most of BOOL with bool in llmath --- indra/llcommon/llstring.cpp | 4 ++-- indra/llcommon/llstring.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f6629803ee..4358e80125 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -308,10 +308,10 @@ S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wle // Given a wstring and an offset in it, returns the length as wstring (i.e., // number of llwchars) of the longest substring that starts at the offset // and whose equivalent utf-16 string does not exceeds the given utf16_length. -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, BOOL *unaligned) +S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned) { const auto end = wstr.length(); - BOOL u = FALSE; + bool u = FALSE; S32 n = woffset + utf16_length; S32 i = woffset; while (i < end) diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 3620a5b211..47dbf967cf 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -687,7 +687,7 @@ LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); // Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr); /** * @brief Properly truncate a utf8 string to a maximum byte count. -- cgit v1.2.3 From 07f54e72766d903292fec985e7c5eb46a6a325ca Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 16 Feb 2024 00:12:57 +0100 Subject: Replaced one overlooked FALSE --- indra/llcommon/llstring.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 4358e80125..ef77b0b1c8 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -311,7 +311,7 @@ S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wle S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned) { const auto end = wstr.length(); - bool u = FALSE; + bool u{ false }; S32 n = woffset + utf16_length; S32 i = woffset; while (i < end) -- cgit v1.2.3 From d0e82ca55670645eacc61fca39bf8667c0840de9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 16 Feb 2024 00:02:04 +0200 Subject: jira-archive-internal#67837 Windows' bulk export of snapshots and textures SL-17661 Viewer was silently failing to 'save selection as' --- indra/llcommon/threadpool.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 3a9a5a2062..e22013312d 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -99,6 +99,10 @@ void LL::ThreadPoolBase::start() LL::ThreadPoolBase::~ThreadPoolBase() { close(); + if (!LLEventPumps::wasDeleted()) + { + LLEventPumps::instance().obtain("LLApp").stopListening(mName); + } } void LL::ThreadPoolBase::close() -- cgit v1.2.3 From 9e854b697a06abed2a0917fb6120445f176764f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Fri, 16 Feb 2024 19:29:51 +0100 Subject: misc: BOOL to bool --- indra/llcommon/llstring.cpp | 2 +- indra/llcommon/llstring.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index ef77b0b1c8..6e6797312b 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1359,7 +1359,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) if (iter != substitutions.end()) { S32 secFromEpoch = 0; - BOOL r = LLStringUtil::convertToS32(iter->second, secFromEpoch); + bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch); if (r) { found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 47dbf967cf..f2741a0e1f 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -382,7 +382,7 @@ public: static void _makeASCII(string_type& string); // Conversion to other data types - static bool convertToBOOL(const string_type& string, BOOL& value); + static bool convertToBOOL(const string_type& string, bool& value); static bool convertToU8(const string_type& string, U8& value); static bool convertToS8(const string_type& string, S8& value); static bool convertToS16(const string_type& string, S16& value); @@ -1804,7 +1804,7 @@ auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt } template -bool LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) +bool LLStringUtilBase::convertToBOOL(const string_type& string, bool& value) { if( string.empty() ) { -- cgit v1.2.3 From 088f2f4f6545ebc2ee01945938a40ae5c87ad27a Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 17 Feb 2024 00:51:13 +0100 Subject: More BOOL to bool replacements primarily in llappearance and llxml --- indra/llcommon/tests/llstring_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index a7aa347222..3e31a5d667 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -231,7 +231,7 @@ namespace tut template<> template<> void string_index_object_t::test<17>() { - BOOL value; + bool value; std::string str_val("1"); ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); str_val = "T"; -- cgit v1.2.3 From 321f283032688f0feddc696654e86f62af07121a Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 19 Feb 2024 15:01:44 +0100 Subject: Replace remaining BOOL with bool llinventory and llmessage --- indra/llcommon/llstringtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index 0a292c8bac..bbf61bb8ac 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -51,7 +51,7 @@ public: ~LLStringTableEntry(); void incCount() { mCount++; } - BOOL decCount() { return --mCount; } + bool decCount() { return --mCount != 0; } char *mString; S32 mCount; -- cgit v1.2.3 From fd7f817171f9842470e83832c67d48065e81ca01 Mon Sep 17 00:00:00 2001 From: Beq Date: Mon, 19 Feb 2024 15:13:25 +0000 Subject: [Linux]gcc is broken for in place template instantiation. --- indra/llcommon/llmutex.h | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 077f810d61..cef58c1c9d 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -82,20 +82,14 @@ public: void lockShared(); void lockExclusive(); template void lock(); - template<> void lock() { lockShared(); } - template<> void lock() { lockExclusive(); } bool trylockShared(); bool trylockExclusive(); template bool trylock(); - template<> bool trylock() { return trylockShared(); } - template<> bool trylock() { return trylockExclusive(); } void unlockShared(); void unlockExclusive(); template void unlock(); - template<> void unlock() { unlockShared(); } - template<> void unlock() { unlockExclusive(); } private: std::shared_mutex mSharedMutex; @@ -107,6 +101,42 @@ private: using const_iterator = std::unordered_map::const_iterator; }; +template<> +inline void LLSharedMutex::lock() +{ + lockShared(); +} + +template<> +inline void LLSharedMutex::lock() +{ + lockExclusive(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockShared(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockExclusive(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + unlockShared(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + unlockExclusive(); +} + // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). class LL_COMMON_API LLCondition : public LLMutex { -- cgit v1.2.3 From 8312a4e5470066d0219ef6e9ec9fc8d90e9e595a Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 19 Feb 2024 18:56:51 +0100 Subject: Include missing llmutex.h in CMake file --- indra/llcommon/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5f4ed2fffa..80bc95ffba 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -194,6 +194,7 @@ set(llcommon_HEADER_FILES llmetrics.h llmetricperformancetester.h llmortician.h + llmutex.h llnametable.h llpointer.h llprofiler.h -- cgit v1.2.3 From b2c271367296744fbbe2262e55d0ea4f8f5ccdc9 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Tue, 20 Feb 2024 00:50:39 +0100 Subject: Convert BOOL to bool in llrender --- indra/llcommon/llmutex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index cef58c1c9d..2b2435da4d 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -64,7 +64,7 @@ protected: mutable LLThread::id_t mLockingThread; #if MUTEX_DEBUG - std::unordered_map mIsLocked; + std::unordered_map mIsLocked; #endif }; -- cgit v1.2.3 From 3ffe63b8a4e8a3ceda3f6d204e4b5bb0c80d0870 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 21 Feb 2024 16:49:48 +0100 Subject: Convert remaining BOOLs in llxml and introduce std::string_view --- indra/llcommon/llstring.cpp | 14 +++++++------- indra/llcommon/llstring.h | 15 ++++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 6e6797312b..bdd4c2c4bb 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -765,7 +765,7 @@ std::wstring windows_message(DWORD error) return out.str(); } -boost::optional llstring_getoptenv(const std::string& key) +std::optional llstring_getoptenv(const std::string& key) { auto wkey = ll_convert_string_to_wide(key); // Take a wild guess as to how big the buffer should be. @@ -783,8 +783,8 @@ boost::optional llstring_getoptenv(const std::string& key) // did that (ultimately) succeed? if (n) { - // great, return populated boost::optional - return boost::optional(&buffer[0]); + // great, return populated std::optional + return std::make_optional(&buffer[0]); } // not successful @@ -795,7 +795,7 @@ boost::optional llstring_getoptenv(const std::string& key) LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " << windows_message(last_error) << LL_ENDL; } - // return empty boost::optional + // return empty std::optional return {}; } @@ -806,12 +806,12 @@ boost::optional llstring_getoptenv(const std::string& key) auto found = getenv(key.c_str()); if (found) { - // return populated boost::optional - return boost::optional(found); + // return populated std::optional + return std::make_optional(found); } else { - // return empty boost::optional + // return empty std::optional return {}; } } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index f2741a0e1f..6893b8ebff 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -28,8 +28,9 @@ #define LL_LLSTRING_H #include -#include +#include #include +#include #include #include // std::wcslen() //#include @@ -345,7 +346,7 @@ public: * (key is always UTF-8) * detect absence by (! return value) */ - static boost::optional getoptenv(const std::string& key); + static std::optional getoptenv(const std::string& key); static void addCRLF(string_type& string); static void removeCRLF(string_type& string); @@ -819,11 +820,11 @@ STRING windows_message() { return windows_message(GetLastError()); } //@} -LL_COMMON_API boost::optional llstring_getoptenv(const std::string& key); +LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); #else // ! LL_WINDOWS -LL_COMMON_API boost::optional llstring_getoptenv(const std::string& key); +LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); #endif // ! LL_WINDOWS @@ -1773,17 +1774,17 @@ bool LLStringUtilBase::endsWith( // static template -auto LLStringUtilBase::getoptenv(const std::string& key) -> boost::optional +auto LLStringUtilBase::getoptenv(const std::string& key) -> std::optional { auto found(llstring_getoptenv(key)); if (found) { - // return populated boost::optional + // return populated std::optional return { ll_convert(*found) }; } else { - // empty boost::optional + // empty std::optional return {}; } } -- cgit v1.2.3 From 855eea7ddf9e1de9226036ca94ccb03ac0e311b9 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 21 Feb 2024 19:19:57 +0200 Subject: viewer#851 Xcode buildfix --- indra/llcommon/llstring.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index bdd4c2c4bb..48551ab375 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -801,7 +801,7 @@ std::optional llstring_getoptenv(const std::string& key) #else // ! LL_WINDOWS -boost::optional llstring_getoptenv(const std::string& key) +std::optional llstring_getoptenv(const std::string& key) { auto found = getenv(key.c_str()); if (found) -- cgit v1.2.3 From 60d3dd98a44230c21803c1606552ee098ed9fa7c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 21 Feb 2024 21:05:14 +0100 Subject: Convert remaining BOOL to bool --- indra/llcommon/tests/llstring_test.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 3e31a5d667..1e4bc1c910 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -80,12 +80,12 @@ namespace tut void string_index_object_t::test<3>() { std::string str("Len=5"); - ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == TRUE && - LLStringUtil::isValidIndex(str, 5) == TRUE && - LLStringUtil::isValidIndex(str, 6) == FALSE); + ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true && + LLStringUtil::isValidIndex(str, 5) == true && + LLStringUtil::isValidIndex(str, 6) == false); std::string str1; - ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == FALSE); + ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false); } template<> template<> @@ -153,10 +153,10 @@ namespace tut void string_index_object_t::test<10>() { std::string str_val("Second"); - ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == TRUE); - ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == FALSE); + ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true); + ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false); std::string str_val2(""); - ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == FALSE); + ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false); } template<> template<> @@ -206,10 +206,10 @@ namespace tut void string_index_object_t::test<15>() { std::string str_val("Hello.\n\r\t"); - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == TRUE); + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true); str_val = "ABC "; - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == FALSE); + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false); } template<> template<> @@ -457,17 +457,17 @@ namespace tut std::string lhs_str("PROgraM12files"); std::string rhs_str("PROgram12Files"); ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); - ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == TRUE); + ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true); lhs_str = "PROgram12Files"; rhs_str = "PROgram12Files"; ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); - ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == FALSE); + ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); lhs_str = "PROgram12Files"; rhs_str = "PROgRAM12FILES"; ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); - ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == FALSE); + ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); } template<> template<> -- cgit v1.2.3 From 0cc3a4bf680da63f48a1fcaf1c4b94ce4d8dffb7 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 13 Mar 2024 09:40:20 +0100 Subject: Remove obsolete LLSINGLETON_C11 and LLSINGLETON_EMPTY_CTOR_C11 macros --- indra/llcommon/llsingleton.h | 15 --------------- 1 file changed, 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index cbe5ab6406..5ca6b65259 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -806,17 +806,6 @@ private: \ friend class LLSingleton; \ DERIVED_CLASS(__VA_ARGS__) -/** - * A slight variance from the above, but includes the "override" keyword - */ -#define LLSINGLETON_C11(DERIVED_CLASS) \ -private: \ - /* implement LLSingleton pure virtual method whose sole purpose */ \ - /* is to remind people to use this macro */ \ - virtual void you_must_use_LLSINGLETON_macro() override {} \ - friend class LLSingleton; \ - DERIVED_CLASS() - /** * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton * subclass body when the constructor is trivial: @@ -835,10 +824,6 @@ private: \ /* LLSINGLETON() is carefully implemented to permit exactly this */ \ LLSINGLETON(DERIVED_CLASS) {} -#define LLSINGLETON_EMPTY_CTOR_C11(DERIVED_CLASS) \ - /* LLSINGLETON() is carefully implemented to permit exactly this */ \ - LLSINGLETON_C11(DERIVED_CLASS) {} - // Relatively unsafe singleton implementation that is much faster // and simpler than LLSingleton, but has no dependency tracking // or inherent thread safety and requires manual invocation of -- cgit v1.2.3 From e242c129f9d56161496a50397b79da1e444e8de4 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Fri, 22 Mar 2024 16:21:02 -0700 Subject: Add Tracy categories for WebRTC Voice Also: * Fix a few crashes. * Only send position data when it changes. --- indra/llcommon/llprofilercategories.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h index 617431f629..0de343d020 100644 --- a/indra/llcommon/llprofilercategories.h +++ b/indra/llcommon/llprofilercategories.h @@ -1,5 +1,5 @@ /** - * @file llprofiler_ategories.h + * @file llprofilercategories.h * @brief Profiling categories to minimize Tracy memory usage when viewing captures. * * $LicenseInfo:firstyear=2022&license=viewerlgpl$ @@ -33,7 +33,7 @@ // LL_PROFILER_CATEGORY_ENABLE_DRAWPOOL // LL_PROFILER_CATEGORY_ENABLE_LLSD // LL_PROFILER_CATEGORY_ENABLE_MEMORY -// LL_PROFILER_CATEGORY_ENABLE_SHADERS +// LL_PROFILER_CATEGORY_ENABLE_SHADER // // NOTE: You can still manually use: // LL_PROFILE_ZONE_SCOPED(); @@ -67,6 +67,7 @@ #define LL_PROFILER_CATEGORY_ENABLE_VERTEX 1 #define LL_PROFILER_CATEGORY_ENABLE_VOLUME 1 #define LL_PROFILER_CATEGORY_ENABLE_WIN32 1 +#define LL_PROFILER_CATEGORY_ENABLE_VOICE 1 #if LL_PROFILER_CATEGORY_ENABLE_APP #define LL_PROFILE_ZONE_NAMED_CATEGORY_APP LL_PROFILE_ZONE_NAMED @@ -276,5 +277,13 @@ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32 #endif +#if LL_PROFILER_CATEGORY_ENABLE_VOICE +#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE LL_PROFILE_ZONE_NAMED +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE LL_PROFILE_ZONE_SCOPED +#else +#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE(name) +#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE +#endif + #endif // LL_PROFILER_CATEGORIES_H -- cgit v1.2.3 From 17e1f3692c5c1e9cbc6ba6895b312a8baae9aec2 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Fri, 5 Apr 2024 19:03:58 -0400 Subject: Port from JsonCPP to Boost.Json for json parsing and serializing (#1054) --- indra/llcommon/CMakeLists.txt | 2 -- indra/llcommon/llsdjson.cpp | 80 +++++++++++++++++++++++++------------------ indra/llcommon/llsdjson.h | 6 ++-- 3 files changed, 50 insertions(+), 38 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 26955cfc08..50079abb96 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -9,7 +9,6 @@ include(bugsplat) include(Linking) include(Boost) include(LLSharedLibs) -include(JsonCpp) include(Copy3rdPartyLibs) include(ZLIBNG) include(URIPARSER) @@ -278,7 +277,6 @@ target_link_libraries( llcommon ll::apr ll::expat - ll::jsoncpp ll::zlib-ng ll::boost ll::uriparser diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 8caaaee534..2fe24693dd 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -31,46 +31,56 @@ #include "llsdjson.h" +#include "llsdutil.h" #include "llerror.h" #include "../llmath/llmath.h" +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4702) // compiler thinks unreachable code +#endif +#include +#if LL_WINDOWS +#pragma warning (pop) +#endif + + + //========================================================================= -LLSD LlsdFromJson(const Json::Value &val) +LLSD LlsdFromJson(const boost::json::value& val) { LLSD result; - switch (val.type()) + switch (val.kind()) { default: - case Json::nullValue: - break; - case Json::intValue: - result = LLSD(static_cast(val.asInt())); + case boost::json::kind::null: break; - case Json::uintValue: - result = LLSD(static_cast(val.asUInt())); + case boost::json::kind::int64: + case boost::json::kind::uint64: + result = LLSD(val.to_number()); break; - case Json::realValue: - result = LLSD(static_cast(val.asDouble())); + case boost::json::kind::double_: + result = LLSD(val.to_number()); break; - case Json::stringValue: - result = LLSD(static_cast(val.asString())); + case boost::json::kind::string: + result = LLSD(boost::json::value_to(val)); break; - case Json::booleanValue: - result = LLSD(static_cast(val.asBool())); + case boost::json::kind::bool_: + result = LLSD(val.as_bool()); break; - case Json::arrayValue: + case boost::json::kind::array: result = LLSD::emptyArray(); - for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + for (const auto &element : val.as_array()) { - result.append(LlsdFromJson((*it))); + result.append(LlsdFromJson(element)); } break; - case Json::objectValue: + case boost::json::kind::object: result = LLSD::emptyMap(); - for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + for (const auto& element : val.as_object()) { - result[it.memberName()] = LlsdFromJson((*it)); + result[element.key()] = LlsdFromJson(element.value()); } break; } @@ -78,44 +88,48 @@ LLSD LlsdFromJson(const Json::Value &val) } //========================================================================= -Json::Value LlsdToJson(const LLSD &val) +boost::json::value LlsdToJson(const LLSD &val) { - Json::Value result; + boost::json::value result; switch (val.type()) { case LLSD::TypeUndefined: - result = Json::Value::null; + result = nullptr; break; case LLSD::TypeBoolean: - result = Json::Value(static_cast(val.asBoolean())); + result = val.asBoolean(); break; case LLSD::TypeInteger: - result = Json::Value(static_cast(val.asInteger())); + result = val.asInteger(); break; case LLSD::TypeReal: - result = Json::Value(static_cast(val.asReal())); + result = val.asReal(); break; case LLSD::TypeURI: case LLSD::TypeDate: case LLSD::TypeUUID: case LLSD::TypeString: - result = Json::Value(val.asString()); + result = val.asString(); break; case LLSD::TypeMap: - result = Json::Value(Json::objectValue); - for (LLSD::map_const_iterator it = val.beginMap(); it != val.endMap(); ++it) + { + boost::json::object& obj = result.emplace_object(); + for (const auto& llsd_dat : llsd::inMap(val)) { - result[it->first] = LlsdToJson(it->second); + obj[llsd_dat.first] = LlsdToJson(llsd_dat.second); } break; + } case LLSD::TypeArray: - result = Json::Value(Json::arrayValue); - for (LLSD::array_const_iterator it = val.beginArray(); it != val.endArray(); ++it) + { + boost::json::array& json_array = result.emplace_array(); + for (const auto& llsd_dat : llsd::inArray(val)) { - result.append(LlsdToJson(*it)); + json_array.push_back(LlsdToJson(llsd_dat)); } break; + } case LLSD::TypeBinary: default: LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h index e56cf03b45..7173e26046 100644 --- a/indra/llcommon/llsdjson.h +++ b/indra/llcommon/llsdjson.h @@ -34,7 +34,7 @@ #include "stdtypes.h" #include "llsd.h" -#include "json/value.h" +#include /// Convert a parsed JSON structure into LLSD maintaining member names and /// array indexes. @@ -53,7 +53,7 @@ /// /// For maps and arrays child entries will be converted and added to the structure. /// Order is preserved for an array but not for objects. -LLSD LlsdFromJson(const Json::Value &val); +LLSD LlsdFromJson(const boost::json::value &val); /// Convert an LLSD object into Parsed JSON object maintaining member names and /// array indexs. @@ -72,6 +72,6 @@ LLSD LlsdFromJson(const Json::Value &val); /// TypeMap | object /// TypeArray | array /// TypeBinary | unsupported -Json::Value LlsdToJson(const LLSD &val); +boost::json::value LlsdToJson(const LLSD &val); #endif // LL_LLSDJSON_H -- cgit v1.2.3 From 7fc5f7e649c564fa8479a72a45459d0cc427d0f8 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Thu, 2 May 2024 10:57:39 -0500 Subject: #1354 Make coroutines use LLCoros::Mutex instead of LLMutex (#1356) * #1354 Make coroutines use LLCoros::Mutex instead of LLMutex * #1354 Fix some more unsafe coroutine executions. * #1354 Implement changes requested by Nat --- indra/llcommon/llcommon.cpp | 4 +--- indra/llcommon/llcoros.cpp | 17 +++++++++++++++++ indra/llcommon/llcoros.h | 10 ++++++++++ indra/llcommon/llerror.cpp | 12 ++++++------ indra/llcommon/llevents.cpp | 10 +++++----- indra/llcommon/llevents.h | 3 ++- indra/llcommon/llfixedbuffer.h | 3 ++- indra/llcommon/llmutex.cpp | 18 +++++++++++++++--- indra/llcommon/llrefcount.h | 7 ------- indra/llcommon/llthread.cpp | 24 ------------------------ 10 files changed, 58 insertions(+), 50 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 6e988260a9..30aefa3134 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -128,8 +128,7 @@ void LLCommon::initClass() sAprInitialized = TRUE; } LLTimer::initClass(); - LLThreadSafeRefCount::initThreadSafeRefCount(); - assert_main_thread(); // Make sure we record the main thread + assert_main_thread(); // Make sure we record the main thread if (!sMasterThreadRecorder) { sMasterThreadRecorder = new LLTrace::ThreadRecorder(); @@ -143,7 +142,6 @@ void LLCommon::cleanupClass() delete sMasterThreadRecorder; sMasterThreadRecorder = NULL; LLTrace::set_master_thread_recorder(NULL); - LLThreadSafeRefCount::cleanupThreadSafeRefCount(); SUBSYSTEM_CLEANUP_DBG(LLTimer); if (sAprInitialized) { diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index c13900f74a..219c65fbb8 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -61,6 +61,23 @@ #include #endif +// static +bool LLCoros::on_main_coro() +{ + if (!LLCoros::instanceExists() || LLCoros::getName().empty()) + { + return true; + } + + return false; +} + +// static +bool LLCoros::on_main_thread_main_coro() +{ + return on_main_coro() && on_main_thread(); +} + // static LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) { diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index fd878f20ad..00650a2454 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -94,6 +94,16 @@ class LL_COMMON_API LLCoros: public LLSingleton void cleanupSingleton() override; public: + // For debugging, return true if on the main coroutine for the current thread + // Code that should not be executed from a coroutine should be protected by + // llassert(LLCoros::on_main_coro()) + static bool on_main_coro(); + + // For debugging, return true if on the main thread and not in a coroutine + // Non-thread-safe code in the main loop should be protected by + // llassert(LLCoros::on_main_thread_main_coro()) + static bool on_main_thread_main_coro(); + /// The viewer's use of the term "coroutine" became deeply embedded before /// the industry term "fiber" emerged to distinguish userland threads from /// simpler, more transient kinds of coroutines. Semantically they've diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 0f48ce16b2..74b138a53b 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -506,7 +506,7 @@ namespace LLError::TimeFunction mTimeFunction; Recorders mRecorders; - LLMutex mRecorderMutex; + LLCoros::Mutex mRecorderMutex; int mShouldLogCallCounter; @@ -1044,7 +1044,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); + LLCoros::LockType lock(s->mRecorderMutex); s->mRecorders.push_back(recorder); } @@ -1055,7 +1055,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); + LLCoros::LockType lock(s->mRecorderMutex); s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), s->mRecorders.end()); } @@ -1104,7 +1104,7 @@ namespace LLError std::shared_ptr findRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); + LLCoros::LockType lock(s->mRecorderMutex); return findRecorderPos(s).first; } @@ -1115,7 +1115,7 @@ namespace LLError bool removeRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); + LLCoros::LockType lock(s->mRecorderMutex); auto found = findRecorderPos(s); if (found.first) { @@ -1221,7 +1221,7 @@ namespace std::string escaped_message; - LLMutexLock lock(&s->mRecorderMutex); + LLCoros::LockType lock(s->mRecorderMutex); for (LLError::RecorderPtr& r : s->mRecorders) { if (!r->enabled()) diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 70931f3a65..8006f9d059 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -382,7 +382,7 @@ std::string LLEventPump::inventName(const std::string& pfx) void LLEventPump::clear() { - LLMutexLock lock(&mConnectionListMutex); + LLCoros::LockType lock(mConnectionListMutex); // Destroy the original LLStandardSignal instance, replacing it with a // whole new one. mSignal = std::make_shared(); @@ -394,7 +394,7 @@ void LLEventPump::reset() { // Resetting mSignal is supposed to disconnect everything on its own // But due to crash on 'reset' added explicit cleanup to get more data - LLMutexLock lock(&mConnectionListMutex); + LLCoros::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator iter = mConnections.begin(); ConnectionMap::const_iterator end = mConnections.end(); while (iter!=end) @@ -419,7 +419,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL return LLBoundListener(); } - LLMutexLock lock(&mConnectionListMutex); + LLCoros::LockType lock(mConnectionListMutex); float nodePosition = 1.0; @@ -582,7 +582,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL LLBoundListener LLEventPump::getListener(const std::string& name) { - LLMutexLock lock(&mConnectionListMutex); + LLCoros::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator found = mConnections.find(name); if (found != mConnections.end()) { @@ -594,7 +594,7 @@ LLBoundListener LLEventPump::getListener(const std::string& name) void LLEventPump::stopListening(const std::string& name) { - LLMutexLock lock(&mConnectionListMutex); + LLCoros::LockType lock(mConnectionListMutex); ConnectionMap::iterator found = mConnections.find(name); if (found != mConnections.end()) { diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index bebcfacdcb..df54a6546d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -61,6 +61,7 @@ #include "llstl.h" #include "llexception.h" #include "llhandle.h" +#include "llcoros.h" /*==========================================================================*| // override this to allow binding free functions with more parameters @@ -601,7 +602,7 @@ private: LLHandle mRegistry; std::string mName; - LLMutex mConnectionListMutex; + LLCoros::Mutex mConnectionListMutex; protected: virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h index 554cf48a4c..b3bef7520b 100644 --- a/indra/llcommon/llfixedbuffer.h +++ b/indra/llcommon/llfixedbuffer.h @@ -33,6 +33,7 @@ #include "llstring.h" #include "llthread.h" #include "llerrorcontrol.h" +#include "llcoros.h" // fixed buffer implementation class LL_COMMON_API LLFixedBuffer : public LLLineBuffer @@ -58,7 +59,7 @@ protected: void addWLine(const LLWString& line); protected: - LLMutex mMutex ; + LLCoros::Mutex mMutex ; }; #endif //LL_FIXED_BUFFER_H diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 0273dd5970..5d93d6e72b 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -28,6 +28,7 @@ #include "llmutex.h" #include "llthread.h" #include "lltimer.h" +#include "llcoros.h" //============================================================================ @@ -44,7 +45,17 @@ LLMutex::~LLMutex() void LLMutex::lock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + + // LLMutex is not coroutine aware and should not be used from a coroutine + // If your code is running in a coroutine, you should use LLCoros::Mutex instead + // NOTE: If the stack trace you're staring at contains non-thread-safe code, + // you should use LLAppViewer::instance().postToMainThread() to shuttle execution + // back to the main loop. + // NOTE: If you got here from seeing this assert in your log and you're not seeing + // a stack trace that points here, put a breakpoint in on_main_coro and try again. + llassert(LLCoros::on_main_coro()); + if(isSelfLocked()) { //redundant lock mCount++; @@ -66,7 +77,8 @@ void LLMutex::lock() void LLMutex::unlock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + if (mCount > 0) { //not the root unlock mCount--; @@ -111,7 +123,7 @@ LLThread::id_t LLMutex::lockingThread() const bool LLMutex::trylock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if(isSelfLocked()) { //redundant lock mCount++; diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index 15e7175fc8..109c29c0c9 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -89,13 +89,6 @@ private: class LL_COMMON_API LLThreadSafeRefCount { -public: - static void initThreadSafeRefCount(); // creates sMutex - static void cleanupThreadSafeRefCount(); // destroys sMutex - -private: - static LLMutex* sMutex; - protected: virtual ~LLThreadSafeRefCount(); // use unref() diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index cd4975d9d3..ef66c36827 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -421,30 +421,6 @@ void LLThread::unlockData() //============================================================================ -//---------------------------------------------------------------------------- - -//static -LLMutex* LLThreadSafeRefCount::sMutex = 0; - -//static -void LLThreadSafeRefCount::initThreadSafeRefCount() -{ - if (!sMutex) - { - sMutex = new LLMutex(); - } -} - -//static -void LLThreadSafeRefCount::cleanupThreadSafeRefCount() -{ - delete sMutex; - sMutex = NULL; -} - - -//---------------------------------------------------------------------------- - LLThreadSafeRefCount::LLThreadSafeRefCount() : mRef(0) { -- cgit v1.2.3 From f9473e8afcb624cc1b101195bf15943ec372b56f Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 6 May 2024 16:52:34 +0200 Subject: secondlife/viewer#1333 BOOL to bool conversion leftovers: ternaries --- indra/llcommon/StackWalker.cpp | 2 +- indra/llcommon/llapp.cpp | 2 +- indra/llcommon/llmutex.cpp | 6 +++--- indra/llcommon/llnametable.h | 2 +- indra/llcommon/llqueuedthread.h | 2 +- indra/llcommon/llregistry.h | 2 +- indra/llcommon/llsdserialize.cpp | 2 +- indra/llcommon/llstl.h | 2 +- indra/llcommon/llstring.h | 2 +- indra/llcommon/llthread.h | 8 ++++---- indra/llcommon/lltimer.cpp | 9 ++++----- indra/llcommon/lluuid.h | 2 +- indra/llcommon/llworkerthread.cpp | 2 +- indra/llcommon/llworkerthread.h | 2 +- 14 files changed, 22 insertions(+), 23 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index 4ecff4ee69..2c1bc47d0e 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -1256,7 +1256,7 @@ bool StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con cleanup: if (pSym) free( pSym ); - if (bLastEntryCalled == false) + if (!bLastEntryCalled) this->OnCallstackEntry(lastEntry, csEntry); if (context == NULL) diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 8275f80f75..2f41c039f2 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -474,7 +474,7 @@ void LLApp::disableCrashlogger() // static bool LLApp::isCrashloggerDisabled() { - return (sDisableCrashlogger == true); + return sDisableCrashlogger; } // static diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 1c025c286d..3cfdc8304e 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -57,7 +57,7 @@ void LLMutex::lock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info auto id = LLThread::currentID(); - if (mIsLocked[id] != false) + if (mIsLocked[id]) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; mIsLocked[id] = true; #endif @@ -77,7 +77,7 @@ void LLMutex::unlock() #if MUTEX_DEBUG // Access the debug info while we have the lock auto id = LLThread::currentID(); - if (mIsLocked[id] != true) + if (!mIsLocked[id]) LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; mIsLocked[id] = false; #endif @@ -127,7 +127,7 @@ bool LLMutex::trylock() #if MUTEX_DEBUG // Have to have the lock before we can access the debug info auto id = LLThread::currentID(); - if (mIsLocked[id] != false) + if (mIsLocked[id]) LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; mIsLocked[id] = true; #endif diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h index 4f11c595ed..ca4c56f630 100644 --- a/indra/llcommon/llnametable.h +++ b/indra/llcommon/llnametable.h @@ -64,7 +64,7 @@ public: bool checkName(const char *name) const { char *tablename = gStringTable.addString(name); - return mNameMap.count(tablename) ? true : false; + return mNameMap.find(tablename) != mNameMap.end(); } DATA resolveName(const std::string& name) const diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index eac4a3e2cb..70d87e7c04 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -144,7 +144,7 @@ public: void printQueueStats(); virtual size_t getPending(); - bool getThreaded() { return mThreaded ? true : false; } + bool getThreaded() { return mThreaded; } // Request accessors status_t getRequestStatus(handle_t handle); diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index e272d7a9b8..89d56373e1 100644 --- a/indra/llcommon/llregistry.h +++ b/indra/llcommon/llregistry.h @@ -60,7 +60,7 @@ public: bool add(ref_const_key_t key, ref_const_value_t value) { - if (mMap.insert(std::make_pair(key, value)).second == false) + if (!mMap.insert(std::make_pair(key, value)).second) { LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL; return false; diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 76171f2dfd..756fd7c678 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -389,7 +389,7 @@ LLSDParser::~LLSDParser() S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth) { - mCheckLimits = (LLSDSerialize::SIZE_UNLIMITED == max_bytes) ? false : true; + mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes; mMaxBytesLeft = max_bytes; return doParse(istr, data, max_depth); } diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 25131291f9..e39769fe9d 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -532,7 +532,7 @@ bool before(const std::type_info* lhs, const std::type_info* rhs) return strcmp(lhs->name(), rhs->name()) < 0; #else // not Linux, or gcc 4.4+ // Just use before(), as we normally would - return lhs->before(*rhs) ? true : false; + return lhs->before(*rhs); #endif } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 300516c78d..a20a40e205 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -464,7 +464,7 @@ struct LLDictionaryLess public: bool operator()(const std::string& a, const std::string& b) const { - return (LLStringUtil::precedesDict(a, b) ? true : false); + return (LLStringUtil::precedesDict(a, b)); } }; diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 1bcbefe9b2..cf2ae00cd7 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -59,17 +59,17 @@ public: bool isQuitting() const { return (QUITTING == mStatus); } bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); } bool isCrashed() const { return (CRASHED == mStatus); } - + static id_t currentID(); // Return ID of current thread static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. - + public: // PAUSE / RESUME functionality. See source code for important usage notes. // Called from MAIN THREAD. void pause(); void unpause(); - bool isPaused() { return isStopped() || mPaused == true; } - + bool isPaused() { return isStopped() || mPaused; } + // Cause the thread to wake up and check its condition void wake(); diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 42c4911ff1..b1ddfc952c 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -424,7 +424,7 @@ F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() /////////////////////////////////////////////////////////////////////////////// -void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) +void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) { mExpirationTicks = get_clock_count() + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); @@ -441,7 +441,7 @@ F32SecondsImplicit LLTimer::getRemainingTimeF32() const } -bool LLTimer::checkExpirationAndReset(F32 expiration) +bool LLTimer::checkExpirationAndReset(F32 expiration) { U64 cur_ticks = get_clock_count(); if (cur_ticks < mExpirationTicks) @@ -455,10 +455,9 @@ bool LLTimer::checkExpirationAndReset(F32 expiration) } -bool LLTimer::hasExpired() const +bool LLTimer::hasExpired() const { - return (get_clock_count() >= mExpirationTicks) - ? true : false; + return get_clock_count() >= mExpirationTicks; } /////////////////////////////////////////////////////////////////////////////// diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 9c306266e7..54fb5742a2 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -153,7 +153,7 @@ struct lluuid_less { bool operator()(const LLUUID& lhs, const LLUUID& rhs) const { - return (lhs < rhs) ? true : false; + return lhs < rhs; } }; diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 06c74bdba0..231dbe4310 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -284,7 +284,7 @@ bool LLWorkerClass::yield() mWorkerThread->checkPause(); bool res; mMutex.lock(); - res = (getFlags() & WCF_ABORT_REQUESTED) ? true : false; + res = (getFlags() & WCF_ABORT_REQUESTED) != 0; mMutex.unlock(); return res; } diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index eb26c28d3d..a0fe30404d 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -178,7 +178,7 @@ private: void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } U32 getFlags() { return mWorkFlags; } public: - bool getFlags(U32 flags) { return mWorkFlags & flags ? true : false; } + bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; } private: // pure virtuals -- cgit v1.2.3 From 03c4458bdcc6821a3047f93b729d412e274ab9af Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 20 May 2024 13:22:55 -0500 Subject: #1392 GLTF Upload (#1394) * #1392 WIP -- Functional texture upload, stubbed out .bin upload. * #1392 GLTF Upload WIP -- Emulates successful upload Successfully uploads texture Emulates successful .gltf and .bin upload by injecting into local asset cache. Emulates rez from inventory by setting sculpt ID of selected object Currently fails in tinygltf parsing due to missing .bin * Add missing notification * Build fix * #1392 Add boost::json .gltf reading support. * #1392 boost::json GLTF writing prototype * Create gltf/README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * #1392 Add ability to render directly from LL::GLTF::Material * Fix for mac build * Mac build fix * #1392 AssetType and Inventory Type plumbing * #1392 More sane error handling and scheduling of uploads. * #1392 Actually attempt to upload glbin * Mac build fix, upload nudge * Mac build fix * Fix glTF asset uploads to server * Mac build fix (inline not static) * More consistent inline * Add glm, mac nudge. * #1392 For consistency with spec, start using glm over glh:: and LLFoo * Another attempt at placating Mac builds * Another Mac nudge * Mac build take 23 * #1392 Prune LLMatrix4a from GLTF namespace. * #1392 Fix for orientation being off (glm::quat is wxyz, not xyzw) * #1392 WIP -- Actually send the sculpt type and id, nudge readme and alpha rendering * #1392 Working download! * #1394 Add support for GLTFEnabled SimulatorFeature * #1392 Review feedback --------- Co-authored-by: Pepper Linden <3782201+rohvani@users.noreply.github.com> --- indra/llcommon/llassettype.cpp | 2 ++ indra/llcommon/llassettype.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index beddfc53cd..d73b64454a 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -97,6 +97,8 @@ LLAssetDictionary::LLAssetDictionary() addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); + addEntry(LLAssetType::AT_GLTF, new AssetEntry("GLTF", "gltf", "GLTF", true, true, true)); + addEntry(LLAssetType::AT_GLTF_BIN, new AssetEntry("GLTF_BIN", "glbin", "GLTF binary", true, true, true)); addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false)); diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index e8df8574f7..4a35f666ff 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -128,8 +128,10 @@ public: AT_SETTINGS = 56, // Collection of settings AT_MATERIAL = 57, // Render Material + AT_GLTF = 58, // gltf json document + AT_GLTF_BIN = 59, // gltf binary data - AT_COUNT = 58, + AT_COUNT = 60, // +*********************************************************+ // | TO ADD AN ELEMENT TO THIS ENUM: | -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/llcommon/StackWalker.h | 452 +-- indra/llcommon/llapp.cpp | 1492 ++++---- indra/llcommon/llapp.h | 684 ++-- indra/llcommon/llapr.cpp | 1494 ++++---- indra/llcommon/llapr.h | 402 +-- indra/llcommon/llassettype.cpp | 492 +-- indra/llcommon/llbase64.cpp | 154 +- indra/llcommon/llbase64.h | 76 +- indra/llcommon/llcallbacklist.cpp | 460 +-- indra/llcommon/llcommon.cpp | 306 +- indra/llcommon/llcommon.h | 86 +- indra/llcommon/llcrc.cpp | 446 +-- indra/llcommon/llcrc.h | 136 +- indra/llcommon/llerror.cpp | 3324 ++++++++--------- indra/llcommon/lleventemitter.h | 204 +- indra/llcommon/lleventtimer.h | 244 +- indra/llcommon/llframetimer.cpp | 300 +- indra/llcommon/llframetimer.h | 302 +- indra/llcommon/llkeythrottle.h | 662 ++-- indra/llcommon/lllivefile.cpp | 396 +-- indra/llcommon/llmemory.cpp | 708 ++-- indra/llcommon/llmemory.h | 836 ++--- indra/llcommon/llmetricperformancetester.cpp | 666 ++-- indra/llcommon/llmetricperformancetester.h | 430 +-- indra/llcommon/llmortician.cpp | 212 +- indra/llcommon/llmortician.h | 112 +- indra/llcommon/llmutex.cpp | 836 ++--- indra/llcommon/llmutex.h | 542 +-- indra/llcommon/llnametable.h | 206 +- indra/llcommon/llpriqueuemap.h | 290 +- indra/llcommon/llprocess.cpp | 2716 +++++++------- indra/llcommon/llqueuedthread.cpp | 1174 +++--- indra/llcommon/llqueuedthread.h | 356 +- indra/llcommon/llregistry.h | 706 ++-- indra/llcommon/llsdserialize.cpp | 4940 +++++++++++++------------- indra/llcommon/llsdutil.cpp | 2166 +++++------ indra/llcommon/llsdutil.h | 1348 +++---- indra/llcommon/llstacktrace.cpp | 336 +- indra/llcommon/llstl.h | 1426 ++++---- indra/llcommon/llstring.cpp | 3512 +++++++++--------- indra/llcommon/llstring.h | 4078 ++++++++++----------- indra/llcommon/llstringtable.h | 418 +-- indra/llcommon/llsys.cpp | 2828 +++++++-------- indra/llcommon/llsys.h | 348 +- indra/llcommon/llthread.cpp | 944 ++--- indra/llcommon/lltimer.cpp | 1216 +++---- indra/llcommon/lltimer.h | 376 +- indra/llcommon/lluri.cpp | 1516 ++++---- indra/llcommon/lluri.h | 386 +- indra/llcommon/lluuid.cpp | 2154 +++++------ indra/llcommon/lluuid.h | 388 +- indra/llcommon/llworkerthread.cpp | 796 ++--- indra/llcommon/llworkerthread.h | 402 +-- indra/llcommon/tests/llstring_test.cpp | 1742 ++++----- 54 files changed, 26611 insertions(+), 26611 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h index 28b10950e0..c76b07a739 100644 --- a/indra/llcommon/StackWalker.h +++ b/indra/llcommon/StackWalker.h @@ -1,226 +1,226 @@ -/********************************************************************** - * - * StackWalker.h - * - * - * - * $LicenseInfo:firstyear=2016&license=bsd$ - * - * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/ - * - * LICENSE (http://www.opensource.org/licenses/bsd-license.php) - * - * Copyright (c) 2005-2009, Jochen Kalmbach - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 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. - * Neither the name of Jochen Kalmbach 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. - * - * **********************************************************************/ - -#if LL_WINDOWS - -// #pragma once is supported starting with _MCS_VER 1000, -// so we need not to check the version (because we only support _MSC_VER >= 1100)! -#pragma once - -#include - -// special defines for VC5/6 (if no actual PSDK is installed): -#if _MSC_VER < 1300 -typedef unsigned __int64 DWORD64, *PDWORD64; -#if defined(_WIN64) -typedef unsigned __int64 SIZE_T, *PSIZE_T; -#else -typedef unsigned long SIZE_T, *PSIZE_T; -#endif -#endif // _MSC_VER < 1300 - -class StackWalkerInternal; // forward -class StackWalker -{ -public: - typedef enum StackWalkOptions - { - // No addition info will be retrived - // (only the address is available) - RetrieveNone = 0, - - // Try to get the symbol-name - RetrieveSymbol = 1, - - // Try to get the line for this symbol - RetrieveLine = 2, - - // Try to retrieve the module-infos - RetrieveModuleInfo = 4, - - // Also retrieve the version for the DLL/EXE - RetrieveFileVersion = 8, - - // Contains all the abouve - RetrieveVerbose = 0xF, - - // Generate a "good" symbol-search-path - SymBuildPath = 0x10, - - // Also use the public Microsoft-Symbol-Server - SymUseSymSrv = 0x20, - - // Contains all the abouve "Sym"-options - SymAll = 0x30, - - // Contains all options (default) - OptionsAll = 0x3F - } StackWalkOptions; - - StackWalker( - bool verbose = true, - int options = OptionsAll, // 'int' is by design, to combine the enum-flags - LPCSTR szSymPath = NULL, - DWORD dwProcessId = GetCurrentProcessId(), - HANDLE hProcess = GetCurrentProcess() - ); - StackWalker(DWORD dwProcessId, HANDLE hProcess); - virtual ~StackWalker(); - - typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( - HANDLE hProcess, - DWORD64 qwBaseAddress, - PVOID lpBuffer, - DWORD nSize, - LPDWORD lpNumberOfBytesRead, - LPVOID pUserData // optional data, which was passed in "ShowCallstack" - ); - - bool LoadModules(); - - bool ShowCallstack( - bool verbose, - HANDLE hThread = GetCurrentThread(), - const CONTEXT *context = NULL, - PReadProcessMemoryRoutine readMemoryFunction = NULL, - LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback - ); - -#if _MSC_VER >= 1300 -// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" -// in older compilers in order to use it... starting with VC7 we can declare it as "protected" -protected: -#endif - enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols - -protected: - // Entry for each Callstack-Entry - typedef struct CallstackEntry - { - DWORD64 offset; // if 0, we have no valid entry - CHAR name[STACKWALK_MAX_NAMELEN]; - CHAR undName[STACKWALK_MAX_NAMELEN]; - CHAR undFullName[STACKWALK_MAX_NAMELEN]; - DWORD64 offsetFromSmybol; - DWORD offsetFromLine; - DWORD lineNumber; - CHAR lineFileName[STACKWALK_MAX_NAMELEN]; - DWORD symType; - LPCSTR symTypeString; - CHAR moduleName[STACKWALK_MAX_NAMELEN]; - DWORD64 baseOfImage; - CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; - } CallstackEntry; - - enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; - - virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); - virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); - virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); - virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); - virtual void OnOutput(LPCSTR szText); - - StackWalkerInternal *m_sw; - HANDLE m_hProcess; - DWORD m_dwProcessId; - bool m_modulesLoaded; - LPSTR m_szSymPath; - - bool m_verbose; - int m_options; - int m_MaxRecursionCount; - - static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); - - friend StackWalkerInternal; -}; // class StackWalker - - -// The "ugly" assembler-implementation is needed for systems before XP -// If you have a new PSDK and you only compile for XP and later, then you can use -// the "RtlCaptureContext" -// Currently there is no define which determines the PSDK-Version... -// So we just use the compiler-version (and assumes that the PSDK is -// the one which was installed by the VS-IDE) - -// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... -// But I currently use it in x64/IA64 environments... -//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) - -#if defined(_M_IX86) -#ifdef CURRENT_THREAD_VIA_EXCEPTION -// TODO: The following is not a "good" implementation, -// because the callstack is only valid in the "__except" block... -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - EXCEPTION_POINTERS *pExp = NULL; \ - __try { \ - throw 0; \ - } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ - if (pExp != NULL) \ - memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - } while(0); -#else -// The following should be enough for walking the callstack... -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - __asm call x \ - __asm x: pop eax \ - __asm mov c.Eip, eax \ - __asm mov c.Ebp, ebp \ - __asm mov c.Esp, esp \ - } while(0); -#endif - -#else - -// The following is defined for x86 (XP and higher), x64 and IA64: -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - RtlCaptureContext(&c); \ -} while(0); -#endif - -#endif // LL_WINDOWS +/********************************************************************** + * + * StackWalker.h + * + * + * + * $LicenseInfo:firstyear=2016&license=bsd$ + * + * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/ + * + * LICENSE (http://www.opensource.org/licenses/bsd-license.php) + * + * Copyright (c) 2005-2009, Jochen Kalmbach + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 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. + * Neither the name of Jochen Kalmbach 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. + * + * **********************************************************************/ + +#if LL_WINDOWS + +// #pragma once is supported starting with _MCS_VER 1000, +// so we need not to check the version (because we only support _MSC_VER >= 1100)! +#pragma once + +#include + +// special defines for VC5/6 (if no actual PSDK is installed): +#if _MSC_VER < 1300 +typedef unsigned __int64 DWORD64, *PDWORD64; +#if defined(_WIN64) +typedef unsigned __int64 SIZE_T, *PSIZE_T; +#else +typedef unsigned long SIZE_T, *PSIZE_T; +#endif +#endif // _MSC_VER < 1300 + +class StackWalkerInternal; // forward +class StackWalker +{ +public: + typedef enum StackWalkOptions + { + // No addition info will be retrived + // (only the address is available) + RetrieveNone = 0, + + // Try to get the symbol-name + RetrieveSymbol = 1, + + // Try to get the line for this symbol + RetrieveLine = 2, + + // Try to retrieve the module-infos + RetrieveModuleInfo = 4, + + // Also retrieve the version for the DLL/EXE + RetrieveFileVersion = 8, + + // Contains all the abouve + RetrieveVerbose = 0xF, + + // Generate a "good" symbol-search-path + SymBuildPath = 0x10, + + // Also use the public Microsoft-Symbol-Server + SymUseSymSrv = 0x20, + + // Contains all the abouve "Sym"-options + SymAll = 0x30, + + // Contains all options (default) + OptionsAll = 0x3F + } StackWalkOptions; + + StackWalker( + bool verbose = true, + int options = OptionsAll, // 'int' is by design, to combine the enum-flags + LPCSTR szSymPath = NULL, + DWORD dwProcessId = GetCurrentProcessId(), + HANDLE hProcess = GetCurrentProcess() + ); + StackWalker(DWORD dwProcessId, HANDLE hProcess); + virtual ~StackWalker(); + + typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( + HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead, + LPVOID pUserData // optional data, which was passed in "ShowCallstack" + ); + + bool LoadModules(); + + bool ShowCallstack( + bool verbose, + HANDLE hThread = GetCurrentThread(), + const CONTEXT *context = NULL, + PReadProcessMemoryRoutine readMemoryFunction = NULL, + LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback + ); + +#if _MSC_VER >= 1300 +// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" +// in older compilers in order to use it... starting with VC7 we can declare it as "protected" +protected: +#endif + enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols + +protected: + // Entry for each Callstack-Entry + typedef struct CallstackEntry + { + DWORD64 offset; // if 0, we have no valid entry + CHAR name[STACKWALK_MAX_NAMELEN]; + CHAR undName[STACKWALK_MAX_NAMELEN]; + CHAR undFullName[STACKWALK_MAX_NAMELEN]; + DWORD64 offsetFromSmybol; + DWORD offsetFromLine; + DWORD lineNumber; + CHAR lineFileName[STACKWALK_MAX_NAMELEN]; + DWORD symType; + LPCSTR symTypeString; + CHAR moduleName[STACKWALK_MAX_NAMELEN]; + DWORD64 baseOfImage; + CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; + } CallstackEntry; + + enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; + + virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); + virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); + virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); + virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); + virtual void OnOutput(LPCSTR szText); + + StackWalkerInternal *m_sw; + HANDLE m_hProcess; + DWORD m_dwProcessId; + bool m_modulesLoaded; + LPSTR m_szSymPath; + + bool m_verbose; + int m_options; + int m_MaxRecursionCount; + + static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); + + friend StackWalkerInternal; +}; // class StackWalker + + +// The "ugly" assembler-implementation is needed for systems before XP +// If you have a new PSDK and you only compile for XP and later, then you can use +// the "RtlCaptureContext" +// Currently there is no define which determines the PSDK-Version... +// So we just use the compiler-version (and assumes that the PSDK is +// the one which was installed by the VS-IDE) + +// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... +// But I currently use it in x64/IA64 environments... +//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) + +#if defined(_M_IX86) +#ifdef CURRENT_THREAD_VIA_EXCEPTION +// TODO: The following is not a "good" implementation, +// because the callstack is only valid in the "__except" block... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + EXCEPTION_POINTERS *pExp = NULL; \ + __try { \ + throw 0; \ + } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ + if (pExp != NULL) \ + memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + } while(0); +#else +// The following should be enough for walking the callstack... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + __asm call x \ + __asm x: pop eax \ + __asm mov c.Eip, eax \ + __asm mov c.Ebp, ebp \ + __asm mov c.Esp, esp \ + } while(0); +#endif + +#else + +// The following is defined for x86 (XP and higher), x64 and IA64: +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + RtlCaptureContext(&c); \ +} while(0); +#endif + +#endif // LL_WINDOWS diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index b354a60bb0..1388e81656 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -1,746 +1,746 @@ -/** - * @file llapp.cpp - * @brief Implementation of the LLApp class. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llapp.h" - -#include - -#ifdef LL_DARWIN -#include -#include -#include -#endif - -#include "llcommon.h" -#include "llapr.h" -#include "llerrorcontrol.h" -#include "llframetimer.h" -#include "lllivefile.h" -#include "llmemory.h" -#include "llstl.h" // for DeletePointer() -#include "llstring.h" -#include "lleventtimer.h" -#include "stringize.h" -#include "llcleanup.h" -#include "llevents.h" -#include "llsdutil.h" - -// -// Signal handling -#ifndef LL_WINDOWS -# include -# include // for fork() -void setup_signals(); -void default_unix_signal_handler(int signum, siginfo_t *info, void *); - -#if LL_LINUX -#else -// Called by breakpad exception handler after the minidump has been generated. -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded); -#endif - -# if LL_DARWIN -/* OSX doesn't support SIGRT* */ -S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; -S32 LL_HEARTBEAT_SIGNAL = SIGUSR2; -# else // linux or (assumed) other similar unixoid -/* We want reliable delivery of our signals - SIGRT* is it. */ -/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */ -/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a - nonconstant result so these are not consts and cannot be used in constant- - expressions. SIGRTMAX may return -1 on rare broken setups. */ -S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1; -S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2; -# endif // LL_DARWIN -#endif // !LL_WINDOWS - -// the static application instance -LLApp* LLApp::sApplication = NULL; - -// Allows the generation of core files for post mortem under gdb -// and disables crashlogger -bool LLApp::sDisableCrashlogger = false; - -// Local flag for whether or not to do logging in signal handlers. -//static -bool LLApp::sLogInSignal = false; - -// static -// Keeps track of application status -LLScalarCond LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; -LLAppErrorHandler LLApp::sErrorHandler = NULL; - - -LLApp::LLApp() -{ - // Set our status to running - setStatus(APP_STATUS_RUNNING); - - LLCommon::initClass(); - - // initialize the options structure. We need to make this an array - // because the structured data will not auto-allocate if we - // reference an invalid location with the [] operator. - mOptions = LLSD::emptyArray(); - LLSD sd; - for(int i = 0; i < PRIORITY_COUNT; ++i) - { - mOptions.append(sd); - } - - // Make sure we clean up APR when we exit - // Don't need to do this if we're cleaning up APR in the destructor - //atexit(ll_cleanup_apr); - - // Set the application to this instance. - sApplication = this; - - // initialize the buffer to write the minidump filename to - // (this is used to avoid allocating memory in the crash handler) - memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH); - mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe"; -} - - -LLApp::~LLApp() -{ - - // reclaim live file memory - std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer()); - mLiveFiles.clear(); - - setStopped(); - - SUBSYSTEM_CLEANUP_DBG(LLCommon); -} - -// static -LLApp* LLApp::instance() -{ - return sApplication; -} - - -LLSD LLApp::getOption(const std::string& name) const -{ - LLSD rv; - LLSD::array_const_iterator iter = mOptions.beginArray(); - LLSD::array_const_iterator end = mOptions.endArray(); - for(; iter != end; ++iter) - { - rv = (*iter)[name]; - if(rv.isDefined()) break; - } - return rv; -} - -bool LLApp::parseCommandOptions(int argc, char** argv) -{ - LLSD commands; - std::string name; - std::string value; - for(int ii = 1; ii < argc; ++ii) - { - if(argv[ii][0] != '-') - { - LL_INFOS() << "Did not find option identifier while parsing token: " - << argv[ii] << LL_ENDL; - return false; - } - int offset = 1; - if(argv[ii][1] == '-') ++offset; - name.assign(&argv[ii][offset]); - if(((ii+1) >= argc) || (argv[ii+1][0] == '-')) - { - // we found another option after this one or we have - // reached the end. simply record that this option was - // found and continue. - int flag = name.compare("logfile"); - if (0 == flag) - { - commands[name] = "log"; - } - else - { - commands[name] = true; - } - - continue; - } - ++ii; - value.assign(argv[ii]); - -#if LL_WINDOWS - //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; - if (argv[ii][start]=='"')start++; - if (argv[ii][end]=='"')end--; - if (start!=0 || end!=slen) - { - value = value.substr (start,end); - } -#endif - - commands[name] = value; - } - setOptionData(PRIORITY_COMMAND_LINE, commands); - return true; -} - -bool LLApp::parseCommandOptions(int argc, wchar_t** wargv) -{ - LLSD commands; - std::string name; - std::string value; - for(int ii = 1; ii < argc; ++ii) - { - if(wargv[ii][0] != '-') - { - LL_INFOS() << "Did not find option identifier while parsing token: " - << wargv[ii] << LL_ENDL; - return false; - } - int offset = 1; - if(wargv[ii][1] == '-') ++offset; - -#if LL_WINDOWS - name.assign(utf16str_to_utf8str(&wargv[ii][offset])); -#else - name.assign(wstring_to_utf8str(&wargv[ii][offset])); -#endif - if(((ii+1) >= argc) || (wargv[ii+1][0] == '-')) - { - // we found another option after this one or we have - // reached the end. simply record that this option was - // found and continue. - int flag = name.compare("logfile"); - if (0 == flag) - { - commands[name] = "log"; - } - else - { - commands[name] = true; - } - - continue; - } - ++ii; - -#if LL_WINDOWS - value.assign(utf16str_to_utf8str((wargv[ii]))); -#else - value.assign(wstring_to_utf8str((wargv[ii]))); -#endif - -#if LL_WINDOWS - //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; - if (wargv[ii][start]=='"')start++; - if (wargv[ii][end]=='"')end--; - if (start!=0 || end!=slen) - { - value = value.substr (start,end); - } -#endif - - commands[name] = value; - } - setOptionData(PRIORITY_COMMAND_LINE, commands); - return true; -} - -void LLApp::manageLiveFile(LLLiveFile* livefile) -{ - if(!livefile) return; - livefile->checkAndReload(); - livefile->addToEventTimer(); - mLiveFiles.push_back(livefile); -} - -bool LLApp::setOptionData(OptionPriority level, LLSD data) -{ - if((level < 0) - || (level >= PRIORITY_COUNT) - || (data.type() != LLSD::TypeMap)) - { - return false; - } - mOptions[level] = data; - return true; -} - -LLSD LLApp::getOptionData(OptionPriority level) -{ - if((level < 0) || (level >= PRIORITY_COUNT)) - { - return LLSD(); - } - return mOptions[level]; -} - -void LLApp::stepFrame() -{ - LLFrameTimer::updateFrameTime(); - LLFrameTimer::updateFrameCount(); - LLEventTimer::updateClass(); - mRunner.run(); -} - -void LLApp::setupErrorHandling(bool second_instance) -{ - // Error handling is done by starting up an error handling thread, which just sleeps and - // occasionally checks to see if the app is in an error state, and sees if it needs to be run. - -#if LL_WINDOWS - -#else // ! LL_WINDOWS - -#if ! defined(LL_BUGSPLAT) - // - // Start up signal handling. - // - // There are two different classes of signals. Synchronous signals are delivered to a specific - // thread, asynchronous signals can be delivered to any thread (in theory) - // - setup_signals(); -#endif // ! LL_BUGSPLAT - -#endif // ! LL_WINDOWS -} - -void LLApp::setErrorHandler(LLAppErrorHandler handler) -{ - LLApp::sErrorHandler = handler; -} - -// static -void LLApp::runErrorHandler() -{ - if (LLApp::sErrorHandler) - { - LLApp::sErrorHandler(); - } - - //LL_INFOS() << "App status now STOPPED" << LL_ENDL; - LLApp::setStopped(); -} - -namespace -{ - -static std::map statusDesc -{ - { LLApp::APP_STATUS_RUNNING, "running" }, - { LLApp::APP_STATUS_QUITTING, "quitting" }, - { LLApp::APP_STATUS_STOPPED, "stopped" }, - { LLApp::APP_STATUS_ERROR, "error" } -}; - -} // anonymous namespace - -// static -void LLApp::setStatus(EAppStatus status) -{ - // notify everyone waiting on sStatus any time its value changes - sStatus.set_all(status); - - // This can also happen very late in the application lifecycle -- don't - // resurrect a deleted LLSingleton - if (! LLEventPumps::wasDeleted()) - { - // notify interested parties of status change - LLSD statsd; - auto found = statusDesc.find(status); - if (found != statusDesc.end()) - { - statsd = found->second; - } - else - { - // unknown status? at least report value - statsd = LLSD::Integer(status); - } - LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); - } -} - - -// static -void LLApp::setError() -{ - // set app status to ERROR - setStatus(APP_STATUS_ERROR); -} - -void LLApp::setDebugFileNames(const std::string &path) -{ - mStaticDebugFileName = path + "static_debug_info.log"; - mDynamicDebugFileName = path + "dynamic_debug_info.log"; -} - -void LLApp::writeMiniDump() -{ -} - -// static -void LLApp::setQuitting() -{ - if (!isExiting()) - { - // If we're already exiting, we don't want to reset our state back to quitting. - LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL; - setStatus(APP_STATUS_QUITTING); - } -} - - -// static -void LLApp::setStopped() -{ - setStatus(APP_STATUS_STOPPED); -} - - -// static -bool LLApp::isStopped() -{ - return (APP_STATUS_STOPPED == sStatus.get()); -} - - -// static -bool LLApp::isRunning() -{ - return (APP_STATUS_RUNNING == sStatus.get()); -} - - -// static -bool LLApp::isError() -{ - return (APP_STATUS_ERROR == sStatus.get()); -} - - -// static -bool LLApp::isQuitting() -{ - return (APP_STATUS_QUITTING == sStatus.get()); -} - -// static -bool LLApp::isExiting() -{ - return isQuitting() || isError(); -} - -void LLApp::disableCrashlogger() -{ - sDisableCrashlogger = true; -} - -// static -bool LLApp::isCrashloggerDisabled() -{ - return sDisableCrashlogger; -} - -// static -int LLApp::getPid() -{ -#if LL_WINDOWS - return GetCurrentProcessId(); -#else - return getpid(); -#endif -} - -#ifndef LL_WINDOWS -void setup_signals() -{ - // - // Set up signal handlers that may result in program termination - // - struct sigaction act; - act.sa_sigaction = default_unix_signal_handler; - sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - - // Synchronous signals -# ifndef LL_BUGSPLAT - sigaction(SIGABRT, &act, NULL); -# endif - sigaction(SIGALRM, &act, NULL); - sigaction(SIGBUS, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigaction(SIGILL, &act, NULL); - sigaction(SIGPIPE, &act, NULL); - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGSYS, &act, NULL); - - sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); - sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); - - // Asynchronous signals that are normally ignored -#ifndef LL_IGNORE_SIGCHLD - sigaction(SIGCHLD, &act, NULL); -#endif // LL_IGNORE_SIGCHLD - sigaction(SIGUSR2, &act, NULL); - - // Asynchronous signals that result in attempted graceful exit - sigaction(SIGHUP, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGINT, &act, NULL); - - // Asynchronous signals that result in core - sigaction(SIGQUIT, &act, NULL); - -} - -void clear_signals() -{ - struct sigaction act; - act.sa_handler = SIG_DFL; - sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - - // Synchronous signals -# ifndef LL_BUGSPLAT - sigaction(SIGABRT, &act, NULL); -# endif - sigaction(SIGALRM, &act, NULL); - sigaction(SIGBUS, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigaction(SIGILL, &act, NULL); - sigaction(SIGPIPE, &act, NULL); - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGSYS, &act, NULL); - - sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); - sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); - - // Asynchronous signals that are normally ignored -#ifndef LL_IGNORE_SIGCHLD - sigaction(SIGCHLD, &act, NULL); -#endif // LL_IGNORE_SIGCHLD - - // Asynchronous signals that result in attempted graceful exit - sigaction(SIGHUP, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGINT, &act, NULL); - - // Asynchronous signals that result in core - sigaction(SIGUSR2, &act, NULL); - sigaction(SIGQUIT, &act, NULL); -} - - - -void default_unix_signal_handler(int signum, siginfo_t *info, void *) -{ - // Unix implementation of synchronous signal handler - // This runs in the thread that threw the signal. - // We do the somewhat sketchy operation of blocking in here until the error handler - // has gracefully stopped the app. - - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL; - } - - - switch (signum) - { - case SIGCHLD: - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL; - } - - return; - case SIGABRT: - // Note that this handler is not set for SIGABRT when using Bugsplat - // Abort just results in termination of the app, no funky error handling. - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL; - } - clear_signals(); - raise(signum); - return; - case SIGINT: - case SIGHUP: - case SIGTERM: - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL; - } - // Graceful exit - // Just set our state to quitting, not error - if (LLApp::isQuitting() || LLApp::isError()) - { - // We're already trying to die, just ignore this signal - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; - } - return; - } - LLApp::setQuitting(); - return; - case SIGALRM: - case SIGPIPE: - case SIGUSR2: - default: - if (signum == LL_SMACKDOWN_SIGNAL || - signum == SIGBUS || - signum == SIGILL || - signum == SIGFPE || - signum == SIGSEGV || - signum == SIGQUIT) - { - if (signum == LL_SMACKDOWN_SIGNAL) - { - // Smackdown treated just like any other app termination, for now - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL; - } - else - { - // Don't log anything, even errors - this is because this signal could happen anywhere. - LLError::setDefaultLevel(LLError::LEVEL_NONE); - } - - // Change the signal that we reraise to SIGABRT, so we generate a core dump. - signum = SIGABRT; - } - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL; - } - if (LLApp::isError()) - { - // Received second fatal signal while handling first, just die right now - // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app. - clear_signals(); - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL; - } - raise(signum); - return; - } - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL; - } - - if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem - { - clear_signals(); - LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL; - raise(signum); - return; - } - - // Flag status to ERROR - LLApp::setError(); - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL; - } - clear_signals(); - raise(signum); - return; - } else { - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; - } - } - } -} - -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded) -{ - // Copy minidump file path into fixed buffer in the app instance to avoid - // heap allocations in a crash handler. - - // path format: /.dmp - auto dirPathLength = strlen(dump_dir); - auto idLength = strlen(minidump_id); - - // The path must not be truncated. - llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH); - - char * path = LLApp::instance()->getMiniDumpFilename(); - auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH; - strncpy(path, dump_dir, remaining); - remaining -= dirPathLength; - path += dirPathLength; - if (remaining > 0 && dirPathLength > 0 && path[-1] != '/') - { - *path++ = '/'; - --remaining; - } - if (remaining > 0) - { - strncpy(path, minidump_id, remaining); - remaining -= idLength; - path += idLength; - strncpy(path, ".dmp", remaining); - } - - LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL; - LLApp::runErrorHandler(); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - clear_signals(); - return false; -#else - return true; -#endif -} -#endif // !WINDOWS - +/** + * @file llapp.cpp + * @brief Implementation of the LLApp class. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llapp.h" + +#include + +#ifdef LL_DARWIN +#include +#include +#include +#endif + +#include "llcommon.h" +#include "llapr.h" +#include "llerrorcontrol.h" +#include "llframetimer.h" +#include "lllivefile.h" +#include "llmemory.h" +#include "llstl.h" // for DeletePointer() +#include "llstring.h" +#include "lleventtimer.h" +#include "stringize.h" +#include "llcleanup.h" +#include "llevents.h" +#include "llsdutil.h" + +// +// Signal handling +#ifndef LL_WINDOWS +# include +# include // for fork() +void setup_signals(); +void default_unix_signal_handler(int signum, siginfo_t *info, void *); + +#if LL_LINUX +#else +// Called by breakpad exception handler after the minidump has been generated. +bool unix_post_minidump_callback(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded); +#endif + +# if LL_DARWIN +/* OSX doesn't support SIGRT* */ +S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; +S32 LL_HEARTBEAT_SIGNAL = SIGUSR2; +# else // linux or (assumed) other similar unixoid +/* We want reliable delivery of our signals - SIGRT* is it. */ +/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */ +/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a + nonconstant result so these are not consts and cannot be used in constant- + expressions. SIGRTMAX may return -1 on rare broken setups. */ +S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1; +S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2; +# endif // LL_DARWIN +#endif // !LL_WINDOWS + +// the static application instance +LLApp* LLApp::sApplication = NULL; + +// Allows the generation of core files for post mortem under gdb +// and disables crashlogger +bool LLApp::sDisableCrashlogger = false; + +// Local flag for whether or not to do logging in signal handlers. +//static +bool LLApp::sLogInSignal = false; + +// static +// Keeps track of application status +LLScalarCond LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; +LLAppErrorHandler LLApp::sErrorHandler = NULL; + + +LLApp::LLApp() +{ + // Set our status to running + setStatus(APP_STATUS_RUNNING); + + LLCommon::initClass(); + + // initialize the options structure. We need to make this an array + // because the structured data will not auto-allocate if we + // reference an invalid location with the [] operator. + mOptions = LLSD::emptyArray(); + LLSD sd; + for(int i = 0; i < PRIORITY_COUNT; ++i) + { + mOptions.append(sd); + } + + // Make sure we clean up APR when we exit + // Don't need to do this if we're cleaning up APR in the destructor + //atexit(ll_cleanup_apr); + + // Set the application to this instance. + sApplication = this; + + // initialize the buffer to write the minidump filename to + // (this is used to avoid allocating memory in the crash handler) + memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH); + mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe"; +} + + +LLApp::~LLApp() +{ + + // reclaim live file memory + std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer()); + mLiveFiles.clear(); + + setStopped(); + + SUBSYSTEM_CLEANUP_DBG(LLCommon); +} + +// static +LLApp* LLApp::instance() +{ + return sApplication; +} + + +LLSD LLApp::getOption(const std::string& name) const +{ + LLSD rv; + LLSD::array_const_iterator iter = mOptions.beginArray(); + LLSD::array_const_iterator end = mOptions.endArray(); + for(; iter != end; ++iter) + { + rv = (*iter)[name]; + if(rv.isDefined()) break; + } + return rv; +} + +bool LLApp::parseCommandOptions(int argc, char** argv) +{ + LLSD commands; + std::string name; + std::string value; + for(int ii = 1; ii < argc; ++ii) + { + if(argv[ii][0] != '-') + { + LL_INFOS() << "Did not find option identifier while parsing token: " + << argv[ii] << LL_ENDL; + return false; + } + int offset = 1; + if(argv[ii][1] == '-') ++offset; + name.assign(&argv[ii][offset]); + if(((ii+1) >= argc) || (argv[ii+1][0] == '-')) + { + // we found another option after this one or we have + // reached the end. simply record that this option was + // found and continue. + int flag = name.compare("logfile"); + if (0 == flag) + { + commands[name] = "log"; + } + else + { + commands[name] = true; + } + + continue; + } + ++ii; + value.assign(argv[ii]); + +#if LL_WINDOWS + //Windows changed command line parsing. Deal with it. + S32 slen = value.length() - 1; + S32 start = 0; + S32 end = slen; + if (argv[ii][start]=='"')start++; + if (argv[ii][end]=='"')end--; + if (start!=0 || end!=slen) + { + value = value.substr (start,end); + } +#endif + + commands[name] = value; + } + setOptionData(PRIORITY_COMMAND_LINE, commands); + return true; +} + +bool LLApp::parseCommandOptions(int argc, wchar_t** wargv) +{ + LLSD commands; + std::string name; + std::string value; + for(int ii = 1; ii < argc; ++ii) + { + if(wargv[ii][0] != '-') + { + LL_INFOS() << "Did not find option identifier while parsing token: " + << wargv[ii] << LL_ENDL; + return false; + } + int offset = 1; + if(wargv[ii][1] == '-') ++offset; + +#if LL_WINDOWS + name.assign(utf16str_to_utf8str(&wargv[ii][offset])); +#else + name.assign(wstring_to_utf8str(&wargv[ii][offset])); +#endif + if(((ii+1) >= argc) || (wargv[ii+1][0] == '-')) + { + // we found another option after this one or we have + // reached the end. simply record that this option was + // found and continue. + int flag = name.compare("logfile"); + if (0 == flag) + { + commands[name] = "log"; + } + else + { + commands[name] = true; + } + + continue; + } + ++ii; + +#if LL_WINDOWS + value.assign(utf16str_to_utf8str((wargv[ii]))); +#else + value.assign(wstring_to_utf8str((wargv[ii]))); +#endif + +#if LL_WINDOWS + //Windows changed command line parsing. Deal with it. + S32 slen = value.length() - 1; + S32 start = 0; + S32 end = slen; + if (wargv[ii][start]=='"')start++; + if (wargv[ii][end]=='"')end--; + if (start!=0 || end!=slen) + { + value = value.substr (start,end); + } +#endif + + commands[name] = value; + } + setOptionData(PRIORITY_COMMAND_LINE, commands); + return true; +} + +void LLApp::manageLiveFile(LLLiveFile* livefile) +{ + if(!livefile) return; + livefile->checkAndReload(); + livefile->addToEventTimer(); + mLiveFiles.push_back(livefile); +} + +bool LLApp::setOptionData(OptionPriority level, LLSD data) +{ + if((level < 0) + || (level >= PRIORITY_COUNT) + || (data.type() != LLSD::TypeMap)) + { + return false; + } + mOptions[level] = data; + return true; +} + +LLSD LLApp::getOptionData(OptionPriority level) +{ + if((level < 0) || (level >= PRIORITY_COUNT)) + { + return LLSD(); + } + return mOptions[level]; +} + +void LLApp::stepFrame() +{ + LLFrameTimer::updateFrameTime(); + LLFrameTimer::updateFrameCount(); + LLEventTimer::updateClass(); + mRunner.run(); +} + +void LLApp::setupErrorHandling(bool second_instance) +{ + // Error handling is done by starting up an error handling thread, which just sleeps and + // occasionally checks to see if the app is in an error state, and sees if it needs to be run. + +#if LL_WINDOWS + +#else // ! LL_WINDOWS + +#if ! defined(LL_BUGSPLAT) + // + // Start up signal handling. + // + // There are two different classes of signals. Synchronous signals are delivered to a specific + // thread, asynchronous signals can be delivered to any thread (in theory) + // + setup_signals(); +#endif // ! LL_BUGSPLAT + +#endif // ! LL_WINDOWS +} + +void LLApp::setErrorHandler(LLAppErrorHandler handler) +{ + LLApp::sErrorHandler = handler; +} + +// static +void LLApp::runErrorHandler() +{ + if (LLApp::sErrorHandler) + { + LLApp::sErrorHandler(); + } + + //LL_INFOS() << "App status now STOPPED" << LL_ENDL; + LLApp::setStopped(); +} + +namespace +{ + +static std::map statusDesc +{ + { LLApp::APP_STATUS_RUNNING, "running" }, + { LLApp::APP_STATUS_QUITTING, "quitting" }, + { LLApp::APP_STATUS_STOPPED, "stopped" }, + { LLApp::APP_STATUS_ERROR, "error" } +}; + +} // anonymous namespace + +// static +void LLApp::setStatus(EAppStatus status) +{ + // notify everyone waiting on sStatus any time its value changes + sStatus.set_all(status); + + // This can also happen very late in the application lifecycle -- don't + // resurrect a deleted LLSingleton + if (! LLEventPumps::wasDeleted()) + { + // notify interested parties of status change + LLSD statsd; + auto found = statusDesc.find(status); + if (found != statusDesc.end()) + { + statsd = found->second; + } + else + { + // unknown status? at least report value + statsd = LLSD::Integer(status); + } + LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); + } +} + + +// static +void LLApp::setError() +{ + // set app status to ERROR + setStatus(APP_STATUS_ERROR); +} + +void LLApp::setDebugFileNames(const std::string &path) +{ + mStaticDebugFileName = path + "static_debug_info.log"; + mDynamicDebugFileName = path + "dynamic_debug_info.log"; +} + +void LLApp::writeMiniDump() +{ +} + +// static +void LLApp::setQuitting() +{ + if (!isExiting()) + { + // If we're already exiting, we don't want to reset our state back to quitting. + LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL; + setStatus(APP_STATUS_QUITTING); + } +} + + +// static +void LLApp::setStopped() +{ + setStatus(APP_STATUS_STOPPED); +} + + +// static +bool LLApp::isStopped() +{ + return (APP_STATUS_STOPPED == sStatus.get()); +} + + +// static +bool LLApp::isRunning() +{ + return (APP_STATUS_RUNNING == sStatus.get()); +} + + +// static +bool LLApp::isError() +{ + return (APP_STATUS_ERROR == sStatus.get()); +} + + +// static +bool LLApp::isQuitting() +{ + return (APP_STATUS_QUITTING == sStatus.get()); +} + +// static +bool LLApp::isExiting() +{ + return isQuitting() || isError(); +} + +void LLApp::disableCrashlogger() +{ + sDisableCrashlogger = true; +} + +// static +bool LLApp::isCrashloggerDisabled() +{ + return sDisableCrashlogger; +} + +// static +int LLApp::getPid() +{ +#if LL_WINDOWS + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + +#ifndef LL_WINDOWS +void setup_signals() +{ + // + // Set up signal handlers that may result in program termination + // + struct sigaction act; + act.sa_sigaction = default_unix_signal_handler; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals +# ifndef LL_BUGSPLAT + sigaction(SIGABRT, &act, NULL); +# endif + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + + // Asynchronous signals that are normally ignored +#ifndef LL_IGNORE_SIGCHLD + sigaction(SIGCHLD, &act, NULL); +#endif // LL_IGNORE_SIGCHLD + sigaction(SIGUSR2, &act, NULL); + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(SIGQUIT, &act, NULL); + +} + +void clear_signals() +{ + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals +# ifndef LL_BUGSPLAT + sigaction(SIGABRT, &act, NULL); +# endif + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + + // Asynchronous signals that are normally ignored +#ifndef LL_IGNORE_SIGCHLD + sigaction(SIGCHLD, &act, NULL); +#endif // LL_IGNORE_SIGCHLD + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(SIGUSR2, &act, NULL); + sigaction(SIGQUIT, &act, NULL); +} + + + +void default_unix_signal_handler(int signum, siginfo_t *info, void *) +{ + // Unix implementation of synchronous signal handler + // This runs in the thread that threw the signal. + // We do the somewhat sketchy operation of blocking in here until the error handler + // has gracefully stopped the app. + + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL; + } + + + switch (signum) + { + case SIGCHLD: + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL; + } + + return; + case SIGABRT: + // Note that this handler is not set for SIGABRT when using Bugsplat + // Abort just results in termination of the app, no funky error handling. + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL; + } + clear_signals(); + raise(signum); + return; + case SIGINT: + case SIGHUP: + case SIGTERM: + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL; + } + // Graceful exit + // Just set our state to quitting, not error + if (LLApp::isQuitting() || LLApp::isError()) + { + // We're already trying to die, just ignore this signal + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; + } + return; + } + LLApp::setQuitting(); + return; + case SIGALRM: + case SIGPIPE: + case SIGUSR2: + default: + if (signum == LL_SMACKDOWN_SIGNAL || + signum == SIGBUS || + signum == SIGILL || + signum == SIGFPE || + signum == SIGSEGV || + signum == SIGQUIT) + { + if (signum == LL_SMACKDOWN_SIGNAL) + { + // Smackdown treated just like any other app termination, for now + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL; + } + else + { + // Don't log anything, even errors - this is because this signal could happen anywhere. + LLError::setDefaultLevel(LLError::LEVEL_NONE); + } + + // Change the signal that we reraise to SIGABRT, so we generate a core dump. + signum = SIGABRT; + } + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL; + } + if (LLApp::isError()) + { + // Received second fatal signal while handling first, just die right now + // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app. + clear_signals(); + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL; + } + raise(signum); + return; + } + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL; + } + + if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem + { + clear_signals(); + LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL; + raise(signum); + return; + } + + // Flag status to ERROR + LLApp::setError(); + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL; + } + clear_signals(); + raise(signum); + return; + } else { + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; + } + } + } +} + +bool unix_post_minidump_callback(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded) +{ + // Copy minidump file path into fixed buffer in the app instance to avoid + // heap allocations in a crash handler. + + // path format: /.dmp + auto dirPathLength = strlen(dump_dir); + auto idLength = strlen(minidump_id); + + // The path must not be truncated. + llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH); + + char * path = LLApp::instance()->getMiniDumpFilename(); + auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH; + strncpy(path, dump_dir, remaining); + remaining -= dirPathLength; + path += dirPathLength; + if (remaining > 0 && dirPathLength > 0 && path[-1] != '/') + { + *path++ = '/'; + --remaining; + } + if (remaining > 0) + { + strncpy(path, minidump_id, remaining); + remaining -= idLength; + path += idLength; + strncpy(path, ".dmp", remaining); + } + + LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL; + LLApp::runErrorHandler(); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + clear_signals(); + return false; +#else + return true; +#endif +} +#endif // !WINDOWS + diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 79b6e03581..ad8912ca88 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -1,342 +1,342 @@ -/** - * @file llapp.h - * @brief Declaration of the LLApp class. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAPP_H -#define LL_LLAPP_H - -#include -#include "llcond.h" -#include "llrun.h" -#include "llsd.h" -#include -#include -// Forward declarations -class LLLiveFile; -#if LL_LINUX -#include -#endif - -typedef void (*LLAppErrorHandler)(); - -#if !LL_WINDOWS -extern S32 LL_SMACKDOWN_SIGNAL; -extern S32 LL_HEARTBEAT_SIGNAL; - -// Clear all of the signal handlers (which we want to do for the child process when we fork -void clear_signals(); - -#endif - -class LL_COMMON_API LLApp -{ -public: - typedef enum e_app_status - { - APP_STATUS_RUNNING, // The application is currently running - the default status - APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up - APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit - APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run - } EAppStatus; - - - LLApp(); - virtual ~LLApp(); - - /** - * @brief Return the static app instance if one was created. - */ - static LLApp* instance(); - - /** @name Runtime options */ - //@{ - /** - * @brief Enumeration to specify option priorities in highest to - * lowest order. - */ - enum OptionPriority - { - PRIORITY_RUNTIME_OVERRIDE, - PRIORITY_COMMAND_LINE, - PRIORITY_SPECIFIC_CONFIGURATION, - PRIORITY_GENERAL_CONFIGURATION, - PRIORITY_DEFAULT, - PRIORITY_COUNT - }; - - /** - * @brief Get the application option at the highest priority. - * - * If the return value is undefined, the option does not exist. - * @param name The name of the option. - * @return Returns the option data. - */ - LLSD getOption(const std::string& name) const; - - /** - * @brief Parse ASCII command line options and insert them into - * application command line options. - * - * The name inserted into the option will have leading option - * identifiers (a minus or double minus) stripped. All options - * with values will be stored as a string, while all options - * without values will be stored as true. - * @param argc The argc passed into main(). - * @param argv The argv passed into main(). - * @return Returns true if the parse succeeded. - */ - bool parseCommandOptions(int argc, char** argv); - - /** - * @brief Parse Unicode command line options and insert them into - * application command line options. - * - * The name inserted into the option will have leading option - * identifiers (a minus or double minus) stripped. All options - * with values will be stored as a string, while all options - * without values will be stored as true. - * @param argc The argc passed into main(). - * @param wargv The wargv passed into main(). - * @return Returns true if the parse succeeded. - */ - bool parseCommandOptions(int argc, wchar_t** wargv); - - /** - * @brief Keep track of live files automatically. - * - * *TODO: it currently uses the addToEventTimer() API - * instead of the runner. I should probalby use the runner. - * - * *NOTE: DO NOT add the livefile instance to any kind of check loop. - * - * @param livefile A valid instance of an LLLiveFile. This LLApp - * instance will delete the livefile instance. - */ - void manageLiveFile(LLLiveFile* livefile); - - /** - * @brief Set the options at the specified priority. - * - * This function completely replaces the options at the priority - * level with the data specified. This function will make sure - * level and data might be valid before doing the replace. - * @param level The priority level of the data. - * @param data The data to set. - * @return Returns true if the option was set. - */ - bool setOptionData(OptionPriority level, LLSD data); - - /** - * @brief Get the option data at the specified priority. - * - * This method is probably not so useful except when merging - * information. - * @param level The priority level of the data. - * @return Returns The data (if any) at the level priority. - */ - LLSD getOptionData(OptionPriority level); - //@} - - - - // - // Main application logic - // - virtual bool init() = 0; // Override to do application initialization - - // - // cleanup() - // - // It's currently assumed that the cleanup() method will only get - // called from the main thread or the error handling thread, as it will - // likely do thread shutdown, among other things. - // - virtual bool cleanup() = 0; // Override to do application cleanup - - // - // frame() - // - // Pass control to the application for a single frame. Returns 'done' - // flag: if frame() returns false, it expects to be called again. - // - virtual bool frame() = 0; // Override for application body logic - - // - // Crash logging - // - void disableCrashlogger(); // Let the OS handle the crashes - static bool isCrashloggerDisabled(); // Get the here above set value - - // - // Application status - // - static void setQuitting(); // Set status to QUITTING, the app is now shutting down - static void setStopped(); // Set status to STOPPED, the app is done running and should exit - static void setError(); // Set status to ERROR, the error handler should run - static bool isStopped(); - static bool isRunning(); - static bool isQuitting(); - static bool isError(); - static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) - static int getPid(); - - // - // Sleep for specified time while still running - // - // For use by a coroutine or thread that performs some maintenance on a - // periodic basis. (See also LLEventTimer.) This method supports the - // pattern of an "infinite" loop that sleeps for some time, performs some - // action, then sleeps again. The trouble with literally sleeping a worker - // thread is that it could potentially sleep right through attempted - // application shutdown. This method avoids that by returning false as - // soon as the application status changes away from APP_STATUS_RUNNING - // (isRunning()). - // - // sleep() returns true if it sleeps undisturbed for the entire specified - // duration. The idea is that you can code 'while sleep(duration) ...', - // which will break the loop once shutdown begins. - // - // Since any time-based LLUnit should be implicitly convertible to - // F32Milliseconds, accept that specific type as a proxy. - static bool sleep(F32Milliseconds duration); - // Allow any duration defined in terms of . - // One can imagine a wonderfully general bidirectional conversion system - // between any type derived from LLUnits::LLUnit and - // any std::chrono::duration -- but that doesn't yet exist. - template - bool sleep(const std::chrono::duration& duration) - { - // wait_for_unequal() has the opposite bool return convention - return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); - } - - /** @name Error handling methods */ - //@{ - /** - * @brief Do our generic platform-specific error-handling setup -- - * signals on unix, structured exceptions on windows. - * - * DO call this method if your app will either spawn children or be - * spawned by a launcher. - * Call just after app object construction. - * (Otherwise your app will crash when getting signals, - * and will not core dump.) - * - * DO NOT call this method if your application has specialized - * error handling code. - */ - void setupErrorHandling(bool mSecondInstance=false); - - void setErrorHandler(LLAppErrorHandler handler); - static void runErrorHandler(); // run shortly after we detect an error - //@} - - // the maximum length of the minidump filename returned by getMiniDumpFilename() - static const U32 MAX_MINDUMP_PATH_LENGTH = 256; - - // change the directory where Breakpad minidump files are written to - void setDebugFileNames(const std::string &path); - - // Return the Google Breakpad minidump filename after a crash. - char *getMiniDumpFilename() { return mMinidumpPath; } - std::string* getStaticDebugFile() { return &mStaticDebugFileName; } - std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } - - // Write out a Google Breakpad minidump file. - void writeMiniDump(); - - - /** - * @brief Get a reference to the application runner - * - * Please use the runner with caution. Since the Runner usage - * pattern is not yet clear, this method just gives access to it - * to add and remove runnables. - * @return Returns the application runner. Do not save the - * pointer past the caller's stack frame. - */ - LLRunner& getRunner() { return mRunner; } - -#ifdef LL_WINDOWS - virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { } -#endif - -public: - typedef std::map string_map; - string_map mOptionMap; // Contains all command-line options and arguments in a map - -protected: - - static void setStatus(EAppStatus status); // Use this to change the application status. - static LLScalarCond sStatus; // Reflects current application status - static bool sDisableCrashlogger; // Let the OS handle crashes for us. - std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. - - std::string mDumpPath; //output path for google breakpad. Dependency workaround. - - /** - * @brief This method is called once a frame to do once a frame tasks. - */ - void stepFrame(); - -private: - // Contains the filename of the minidump file after a crash. - char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH]; - - std::string mStaticDebugFileName; - std::string mDynamicDebugFileName; - - // *NOTE: On Windows, we need a routine to reset the structured - // exception handler when some evil driver has taken it over for - // their own purposes - typedef int(*signal_handler_func)(int signum); - static LLAppErrorHandler sErrorHandler; - - // This is the application level runnable scheduler. - LLRunner mRunner; - - /** @name Runtime option implementation */ - //@{ - - // The application options. - LLSD mOptions; - - // The live files for this application - std::vector mLiveFiles; - //@} - -private: - // the static application instance if it was created. - static LLApp* sApplication; - -#if !LL_WINDOWS - friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); -#endif - -public: - static bool sLogInSignal; -}; - -#endif // LL_LLAPP_H +/** + * @file llapp.h + * @brief Declaration of the LLApp class. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAPP_H +#define LL_LLAPP_H + +#include +#include "llcond.h" +#include "llrun.h" +#include "llsd.h" +#include +#include +// Forward declarations +class LLLiveFile; +#if LL_LINUX +#include +#endif + +typedef void (*LLAppErrorHandler)(); + +#if !LL_WINDOWS +extern S32 LL_SMACKDOWN_SIGNAL; +extern S32 LL_HEARTBEAT_SIGNAL; + +// Clear all of the signal handlers (which we want to do for the child process when we fork +void clear_signals(); + +#endif + +class LL_COMMON_API LLApp +{ +public: + typedef enum e_app_status + { + APP_STATUS_RUNNING, // The application is currently running - the default status + APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up + APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit + APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run + } EAppStatus; + + + LLApp(); + virtual ~LLApp(); + + /** + * @brief Return the static app instance if one was created. + */ + static LLApp* instance(); + + /** @name Runtime options */ + //@{ + /** + * @brief Enumeration to specify option priorities in highest to + * lowest order. + */ + enum OptionPriority + { + PRIORITY_RUNTIME_OVERRIDE, + PRIORITY_COMMAND_LINE, + PRIORITY_SPECIFIC_CONFIGURATION, + PRIORITY_GENERAL_CONFIGURATION, + PRIORITY_DEFAULT, + PRIORITY_COUNT + }; + + /** + * @brief Get the application option at the highest priority. + * + * If the return value is undefined, the option does not exist. + * @param name The name of the option. + * @return Returns the option data. + */ + LLSD getOption(const std::string& name) const; + + /** + * @brief Parse ASCII command line options and insert them into + * application command line options. + * + * The name inserted into the option will have leading option + * identifiers (a minus or double minus) stripped. All options + * with values will be stored as a string, while all options + * without values will be stored as true. + * @param argc The argc passed into main(). + * @param argv The argv passed into main(). + * @return Returns true if the parse succeeded. + */ + bool parseCommandOptions(int argc, char** argv); + + /** + * @brief Parse Unicode command line options and insert them into + * application command line options. + * + * The name inserted into the option will have leading option + * identifiers (a minus or double minus) stripped. All options + * with values will be stored as a string, while all options + * without values will be stored as true. + * @param argc The argc passed into main(). + * @param wargv The wargv passed into main(). + * @return Returns true if the parse succeeded. + */ + bool parseCommandOptions(int argc, wchar_t** wargv); + + /** + * @brief Keep track of live files automatically. + * + * *TODO: it currently uses the addToEventTimer() API + * instead of the runner. I should probalby use the runner. + * + * *NOTE: DO NOT add the livefile instance to any kind of check loop. + * + * @param livefile A valid instance of an LLLiveFile. This LLApp + * instance will delete the livefile instance. + */ + void manageLiveFile(LLLiveFile* livefile); + + /** + * @brief Set the options at the specified priority. + * + * This function completely replaces the options at the priority + * level with the data specified. This function will make sure + * level and data might be valid before doing the replace. + * @param level The priority level of the data. + * @param data The data to set. + * @return Returns true if the option was set. + */ + bool setOptionData(OptionPriority level, LLSD data); + + /** + * @brief Get the option data at the specified priority. + * + * This method is probably not so useful except when merging + * information. + * @param level The priority level of the data. + * @return Returns The data (if any) at the level priority. + */ + LLSD getOptionData(OptionPriority level); + //@} + + + + // + // Main application logic + // + virtual bool init() = 0; // Override to do application initialization + + // + // cleanup() + // + // It's currently assumed that the cleanup() method will only get + // called from the main thread or the error handling thread, as it will + // likely do thread shutdown, among other things. + // + virtual bool cleanup() = 0; // Override to do application cleanup + + // + // frame() + // + // Pass control to the application for a single frame. Returns 'done' + // flag: if frame() returns false, it expects to be called again. + // + virtual bool frame() = 0; // Override for application body logic + + // + // Crash logging + // + void disableCrashlogger(); // Let the OS handle the crashes + static bool isCrashloggerDisabled(); // Get the here above set value + + // + // Application status + // + static void setQuitting(); // Set status to QUITTING, the app is now shutting down + static void setStopped(); // Set status to STOPPED, the app is done running and should exit + static void setError(); // Set status to ERROR, the error handler should run + static bool isStopped(); + static bool isRunning(); + static bool isQuitting(); + static bool isError(); + static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) + static int getPid(); + + // + // Sleep for specified time while still running + // + // For use by a coroutine or thread that performs some maintenance on a + // periodic basis. (See also LLEventTimer.) This method supports the + // pattern of an "infinite" loop that sleeps for some time, performs some + // action, then sleeps again. The trouble with literally sleeping a worker + // thread is that it could potentially sleep right through attempted + // application shutdown. This method avoids that by returning false as + // soon as the application status changes away from APP_STATUS_RUNNING + // (isRunning()). + // + // sleep() returns true if it sleeps undisturbed for the entire specified + // duration. The idea is that you can code 'while sleep(duration) ...', + // which will break the loop once shutdown begins. + // + // Since any time-based LLUnit should be implicitly convertible to + // F32Milliseconds, accept that specific type as a proxy. + static bool sleep(F32Milliseconds duration); + // Allow any duration defined in terms of . + // One can imagine a wonderfully general bidirectional conversion system + // between any type derived from LLUnits::LLUnit and + // any std::chrono::duration -- but that doesn't yet exist. + template + bool sleep(const std::chrono::duration& duration) + { + // wait_for_unequal() has the opposite bool return convention + return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); + } + + /** @name Error handling methods */ + //@{ + /** + * @brief Do our generic platform-specific error-handling setup -- + * signals on unix, structured exceptions on windows. + * + * DO call this method if your app will either spawn children or be + * spawned by a launcher. + * Call just after app object construction. + * (Otherwise your app will crash when getting signals, + * and will not core dump.) + * + * DO NOT call this method if your application has specialized + * error handling code. + */ + void setupErrorHandling(bool mSecondInstance=false); + + void setErrorHandler(LLAppErrorHandler handler); + static void runErrorHandler(); // run shortly after we detect an error + //@} + + // the maximum length of the minidump filename returned by getMiniDumpFilename() + static const U32 MAX_MINDUMP_PATH_LENGTH = 256; + + // change the directory where Breakpad minidump files are written to + void setDebugFileNames(const std::string &path); + + // Return the Google Breakpad minidump filename after a crash. + char *getMiniDumpFilename() { return mMinidumpPath; } + std::string* getStaticDebugFile() { return &mStaticDebugFileName; } + std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } + + // Write out a Google Breakpad minidump file. + void writeMiniDump(); + + + /** + * @brief Get a reference to the application runner + * + * Please use the runner with caution. Since the Runner usage + * pattern is not yet clear, this method just gives access to it + * to add and remove runnables. + * @return Returns the application runner. Do not save the + * pointer past the caller's stack frame. + */ + LLRunner& getRunner() { return mRunner; } + +#ifdef LL_WINDOWS + virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { } +#endif + +public: + typedef std::map string_map; + string_map mOptionMap; // Contains all command-line options and arguments in a map + +protected: + + static void setStatus(EAppStatus status); // Use this to change the application status. + static LLScalarCond sStatus; // Reflects current application status + static bool sDisableCrashlogger; // Let the OS handle crashes for us. + std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. + + std::string mDumpPath; //output path for google breakpad. Dependency workaround. + + /** + * @brief This method is called once a frame to do once a frame tasks. + */ + void stepFrame(); + +private: + // Contains the filename of the minidump file after a crash. + char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH]; + + std::string mStaticDebugFileName; + std::string mDynamicDebugFileName; + + // *NOTE: On Windows, we need a routine to reset the structured + // exception handler when some evil driver has taken it over for + // their own purposes + typedef int(*signal_handler_func)(int signum); + static LLAppErrorHandler sErrorHandler; + + // This is the application level runnable scheduler. + LLRunner mRunner; + + /** @name Runtime option implementation */ + //@{ + + // The application options. + LLSD mOptions; + + // The live files for this application + std::vector mLiveFiles; + //@} + +private: + // the static application instance if it was created. + static LLApp* sApplication; + +#if !LL_WINDOWS + friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); +#endif + +public: + static bool sLogInSignal; +}; + +#endif // LL_LLAPP_H diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 0aa68f28cb..b085f8f5dc 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -1,747 +1,747 @@ -/** - * @file llapr.cpp - * @author Phoenix - * @date 2004-11-28 - * @brief Helper functions for using the apache portable runtime library. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llapr.h" -#include "llmutex.h" -#include "apr_dso.h" - -apr_pool_t *gAPRPoolp = NULL; // Global APR memory pool -LLVolatileAPRPool *LLAPRFile::sAPRFilePoolp = NULL ; //global volatile APR memory pool. - -const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAPRPool - -bool gAPRInitialized = false; - -int abortfunc(int retcode) -{ - LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL; - return 0; -} - -void ll_init_apr() -{ - // Initialize APR and create the global pool - apr_initialize(); - - if (!gAPRPoolp) - { - apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL); - } - - if(!LLAPRFile::sAPRFilePoolp) - { - LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(false) ; - } - - gAPRInitialized = true; -} - - -bool ll_apr_is_initialized() -{ - return gAPRInitialized; -} - -void ll_cleanup_apr() -{ - gAPRInitialized = false; - - LL_DEBUGS("APR") << "Cleaning up APR" << LL_ENDL; - - if (gAPRPoolp) - { - apr_pool_destroy(gAPRPoolp); - gAPRPoolp = NULL; - } - if (LLAPRFile::sAPRFilePoolp) - { - delete LLAPRFile::sAPRFilePoolp ; - LLAPRFile::sAPRFilePoolp = NULL ; - } - apr_terminate(); -} - -// -// -//LLAPRPool -// -LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) - : mParent(parent), - mReleasePoolFlag(releasePoolFlag), - mMaxSize(size), - mPool(NULL) -{ - createAPRPool() ; -} - -LLAPRPool::~LLAPRPool() -{ - releaseAPRPool() ; -} - -void LLAPRPool::createAPRPool() -{ - if(mPool) - { - return ; - } - - mStatus = apr_pool_create(&mPool, mParent); - ll_apr_warn_status(mStatus) ; - - if(mMaxSize > 0) //size is the number of blocks (which is usually 4K), NOT bytes. - { - apr_allocator_t *allocator = apr_pool_allocator_get(mPool); - if (allocator) - { - apr_allocator_max_free_set(allocator, mMaxSize) ; - } - } -} - -void LLAPRPool::releaseAPRPool() -{ - if(!mPool) - { - return ; - } - - if(!mParent || mReleasePoolFlag) - { - apr_pool_destroy(mPool) ; - mPool = NULL ; - } -} - -//virtual -apr_pool_t* LLAPRPool::getAPRPool() -{ - return mPool ; -} - -LLVolatileAPRPool::LLVolatileAPRPool(bool is_local, apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) - : LLAPRPool(parent, size, releasePoolFlag), - mNumActiveRef(0), - mNumTotalRef(0) -{ - //create mutex - if(!is_local) //not a local apr_pool, that is: shared by multiple threads. - { - mMutexp.reset(new std::mutex()); - } -} - -LLVolatileAPRPool::~LLVolatileAPRPool() -{ - //delete mutex - mMutexp.reset(); -} - -// -//define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). -// -//virtual -apr_pool_t* LLVolatileAPRPool::getAPRPool() -{ - return LLVolatileAPRPool::getVolatileAPRPool() ; -} - -apr_pool_t* LLVolatileAPRPool::getVolatileAPRPool() -{ - LLScopedLock lock(mMutexp.get()) ; - - mNumTotalRef++ ; - mNumActiveRef++ ; - - if(!mPool) - { - createAPRPool() ; - } - - return mPool ; -} - -void LLVolatileAPRPool::clearVolatileAPRPool() -{ - LLScopedLock lock(mMutexp.get()); - - if(mNumActiveRef > 0) - { - mNumActiveRef--; - if(mNumActiveRef < 1) - { - if(isFull()) - { - mNumTotalRef = 0 ; - - //destroy the apr_pool. - releaseAPRPool() ; - } - else - { - //This does not actually free the memory, - //it just allows the pool to re-use this memory for the next allocation. - apr_pool_clear(mPool) ; - } - } - } - else - { - llassert_always(mNumActiveRef > 0) ; - } - - llassert(mNumTotalRef <= (FULL_VOLATILE_APR_POOL << 2)) ; -} - -bool LLVolatileAPRPool::isFull() -{ - return mNumTotalRef > FULL_VOLATILE_APR_POOL ; -} - -//--------------------------------------------------------------------- - -bool _ll_apr_warn_status(apr_status_t status, const char* file, int line) -{ - if(APR_SUCCESS == status) return false; -#if !LL_LINUX - char buf[MAX_STRING]; /* Flawfinder: ignore */ - apr_strerror(status, buf, sizeof(buf)); - LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL; -#endif - return true; -} - -void _ll_apr_assert_status(apr_status_t status, const char* file, int line) -{ - llassert(! _ll_apr_warn_status(status, file, line)); -} - -//--------------------------------------------------------------------- -// -// Scope based pool access -// -//--------------------------------------------------------------------- - -class LLAPRFilePoolScope -{ -public: - LLAPRFilePoolScope() : pPool(NULL), mInitialized(false) {} - LLAPRFilePoolScope(LLVolatileAPRPool* poolp) : mInitialized(false) - { - setFilePool(poolp); - } - ~LLAPRFilePoolScope() - { - reset(); - } - apr_pool_t* getVolatileAPRPool(LLVolatileAPRPool* poolp = NULL) - { - if (!pPool) - { - setFilePool(poolp); - } - if (mInitialized) - { - // We need one clear per one get - // At the moment no need to support multiple calls - LL_ERRS() << "LLAPRFilePoolScope is not supposed to be initialized twice" << LL_ENDL; - } - mInitialized = true; - return pPool->getVolatileAPRPool(); - } - void reset() - { - if (mInitialized) - { - pPool->clearVolatileAPRPool(); - } - } - -private: - void setFilePool(LLVolatileAPRPool* poolp = NULL) - { - if (poolp) - { - pPool = poolp; - } - else - { - pPool = LLAPRFile::sAPRFilePoolp; - } - } - - LLVolatileAPRPool *pPool; - bool mInitialized; -}; - -//--------------------------------------------------------------------- -// -// LLAPRFile functions -// -LLAPRFile::LLAPRFile() - : mFile(NULL), - mCurrentFilePoolp(NULL) -{ -} - -LLAPRFile::LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool) - : mFile(NULL), - mCurrentFilePoolp(NULL) -{ - open(filename, flags, pool); -} - -LLAPRFile::~LLAPRFile() -{ - close() ; -} - -apr_status_t LLAPRFile::close() -{ - apr_status_t ret = APR_SUCCESS ; - if(mFile) - { - ret = apr_file_close(mFile); - mFile = NULL ; - } - - if(mCurrentFilePoolp) - { - mCurrentFilePoolp->clearVolatileAPRPool() ; - mCurrentFilePoolp = NULL ; - } - - return ret ; -} - -apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool, S32* sizep) -{ - apr_status_t s ; - - //check if already open some file - llassert_always(!mFile) ; - llassert_always(!mCurrentFilePoolp) ; - - mCurrentFilePoolp = pool ? pool : sAPRFilePoolp; - apr_pool_t* apr_pool = mCurrentFilePoolp->getVolatileAPRPool(); //paired with clear in close() - s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); - - if (s != APR_SUCCESS || !mFile) - { - mFile = NULL ; - - if (sizep) - { - *sizep = 0; - } - } - else if (sizep) - { - S32 file_size = 0; - apr_off_t offset = 0; - if (apr_file_seek(mFile, APR_END, &offset) == APR_SUCCESS) - { - llassert_always(offset <= 0x7fffffff); - file_size = (S32)offset; - offset = 0; - apr_file_seek(mFile, APR_SET, &offset); - } - *sizep = file_size; - } - - if (!mFile) - { - // It will clean pool - close() ; - } - - return s ; -} - -//use gAPRPoolp. -apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, bool use_global_pool) -{ - apr_status_t s; - - //check if already open some file - llassert_always(!mFile) ; - llassert_always(!mCurrentFilePoolp) ; - llassert_always(use_global_pool) ; //be aware of using gAPRPoolp. - - s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, gAPRPoolp); - if (s != APR_SUCCESS || !mFile) - { - mFile = NULL ; - close() ; - return s; - } - - return s; -} - -// File I/O -S32 LLAPRFile::read(void *buf, S32 nbytes) -{ - if(!mFile) - { - LL_WARNS() << "apr mFile is removed by somebody else. Can not read." << LL_ENDL ; - return 0; - } - - apr_size_t sz = nbytes; - apr_status_t s = apr_file_read(mFile, buf, &sz); - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return 0; - } - else - { - llassert_always(sz <= 0x7fffffff); - return (S32)sz; - } -} - -S32 LLAPRFile::write(const void *buf, S32 nbytes) -{ - if(!mFile) - { - LL_WARNS() << "apr mFile is removed by somebody else. Can not write." << LL_ENDL ; - return 0; - } - - apr_size_t sz = nbytes; - apr_status_t s = apr_file_write(mFile, buf, &sz); - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return 0; - } - else - { - llassert_always(sz <= 0x7fffffff); - return (S32)sz; - } -} - -S32 LLAPRFile::seek(apr_seek_where_t where, S32 offset) -{ - return LLAPRFile::seek(mFile, where, offset) ; -} - -// -//******************************************************************************************************************************* -//static components of LLAPRFile -// - -//static -apr_status_t LLAPRFile::close(apr_file_t* file_handle) -{ - apr_status_t ret = APR_SUCCESS ; - if(file_handle) - { - ret = apr_file_close(file_handle); - file_handle = NULL ; - } - - return ret ; -} - -//static -apr_file_t* LLAPRFile::open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags) -{ - apr_status_t s; - apr_file_t* file_handle ; - - - s = apr_file_open(&file_handle, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); - if (s != APR_SUCCESS || !file_handle) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to open filename: " << filename << LL_ENDL; - file_handle = NULL ; - close(file_handle) ; - return NULL; - } - - return file_handle ; -} - -//static -S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) -{ - if(!file_handle) - { - return -1 ; - } - - apr_status_t s; - apr_off_t apr_offset; - if (offset >= 0) - { - apr_offset = (apr_off_t)offset; - s = apr_file_seek(file_handle, where, &apr_offset); - } - else - { - apr_offset = 0; - s = apr_file_seek(file_handle, APR_END, &apr_offset); - } - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return -1; - } - else - { - llassert_always(apr_offset <= 0x7fffffff); - return (S32)apr_offset; - } -} - -//static -S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) -{ - LL_PROFILE_ZONE_SCOPED; - //***************************************** - LLAPRFilePoolScope scope(pool); - apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY); - //***************************************** - if (!file_handle) - { - return 0; - } - - llassert(offset >= 0); - - if (offset > 0) - offset = LLAPRFile::seek(file_handle, APR_SET, offset); - - apr_size_t bytes_read; - if (offset < 0) - { - bytes_read = 0; - } - else - { - bytes_read = nbytes ; - apr_status_t s = apr_file_read(file_handle, buf, &bytes_read); - if (s != APR_SUCCESS) - { - LL_WARNS("APR") << " Attempting to read filename: " << filename << LL_ENDL; - ll_apr_warn_status(s); - bytes_read = 0; - } - else - { - llassert_always(bytes_read <= 0x7fffffff); - } - } - - //***************************************** - close(file_handle) ; - //***************************************** - return (S32)bytes_read; -} - -//static -S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) -{ - LL_PROFILE_ZONE_SCOPED; - apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; - if (offset < 0) - { - flags |= APR_APPEND; - offset = 0; - } - - //***************************************** - LLAPRFilePoolScope scope(pool); - apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), flags); - //***************************************** - if (!file_handle) - { - return 0; - } - - if (offset > 0) - { - offset = LLAPRFile::seek(file_handle, APR_SET, offset); - } - - apr_size_t bytes_written; - if (offset < 0) - { - bytes_written = 0; - } - else - { - bytes_written = nbytes ; - apr_status_t s = apr_file_write(file_handle, buf, &bytes_written); - if (s != APR_SUCCESS) - { - LL_WARNS("APR") << " Attempting to write filename: " << filename << LL_ENDL; - ll_apr_warn_status(s); - bytes_written = 0; - } - else - { - llassert_always(bytes_written <= 0x7fffffff); - } - } - - //***************************************** - LLAPRFile::close(file_handle); - //***************************************** - - return (S32)bytes_written; -} - -//static -bool LLAPRFile::remove(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(filename.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_rename(filename.c_str(), newname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to rename filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::isExist(const std::string& filename, LLVolatileAPRPool* pool, apr_int32_t flags) -{ - apr_file_t* apr_file; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), flags, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return false; - } - else - { - apr_file_close(apr_file) ; - return true; - } -} - -//static -S32 LLAPRFile::size(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_file_t* apr_file; - apr_finfo_t info; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), APR_READ, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return 0; - } - else - { - apr_status_t s = apr_file_info_get(&info, APR_FINFO_SIZE, apr_file); - - apr_file_close(apr_file) ; - - if (s == APR_SUCCESS) - { - return (S32)info.size; - } - else - { - return 0; - } - } -} - -//static -bool LLAPRFile::makeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_dir_make(dirname.c_str(), APR_FPROT_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to make directory: " << dirname << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::removeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(dirname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove directory: " << dirname << LL_ENDL; - return false; - } - return true; -} -// -//end of static components of LLAPRFile -//******************************************************************************************************************************* -// +/** + * @file llapr.cpp + * @author Phoenix + * @date 2004-11-28 + * @brief Helper functions for using the apache portable runtime library. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llapr.h" +#include "llmutex.h" +#include "apr_dso.h" + +apr_pool_t *gAPRPoolp = NULL; // Global APR memory pool +LLVolatileAPRPool *LLAPRFile::sAPRFilePoolp = NULL ; //global volatile APR memory pool. + +const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAPRPool + +bool gAPRInitialized = false; + +int abortfunc(int retcode) +{ + LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL; + return 0; +} + +void ll_init_apr() +{ + // Initialize APR and create the global pool + apr_initialize(); + + if (!gAPRPoolp) + { + apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL); + } + + if(!LLAPRFile::sAPRFilePoolp) + { + LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(false) ; + } + + gAPRInitialized = true; +} + + +bool ll_apr_is_initialized() +{ + return gAPRInitialized; +} + +void ll_cleanup_apr() +{ + gAPRInitialized = false; + + LL_DEBUGS("APR") << "Cleaning up APR" << LL_ENDL; + + if (gAPRPoolp) + { + apr_pool_destroy(gAPRPoolp); + gAPRPoolp = NULL; + } + if (LLAPRFile::sAPRFilePoolp) + { + delete LLAPRFile::sAPRFilePoolp ; + LLAPRFile::sAPRFilePoolp = NULL ; + } + apr_terminate(); +} + +// +// +//LLAPRPool +// +LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) + : mParent(parent), + mReleasePoolFlag(releasePoolFlag), + mMaxSize(size), + mPool(NULL) +{ + createAPRPool() ; +} + +LLAPRPool::~LLAPRPool() +{ + releaseAPRPool() ; +} + +void LLAPRPool::createAPRPool() +{ + if(mPool) + { + return ; + } + + mStatus = apr_pool_create(&mPool, mParent); + ll_apr_warn_status(mStatus) ; + + if(mMaxSize > 0) //size is the number of blocks (which is usually 4K), NOT bytes. + { + apr_allocator_t *allocator = apr_pool_allocator_get(mPool); + if (allocator) + { + apr_allocator_max_free_set(allocator, mMaxSize) ; + } + } +} + +void LLAPRPool::releaseAPRPool() +{ + if(!mPool) + { + return ; + } + + if(!mParent || mReleasePoolFlag) + { + apr_pool_destroy(mPool) ; + mPool = NULL ; + } +} + +//virtual +apr_pool_t* LLAPRPool::getAPRPool() +{ + return mPool ; +} + +LLVolatileAPRPool::LLVolatileAPRPool(bool is_local, apr_pool_t *parent, apr_size_t size, bool releasePoolFlag) + : LLAPRPool(parent, size, releasePoolFlag), + mNumActiveRef(0), + mNumTotalRef(0) +{ + //create mutex + if(!is_local) //not a local apr_pool, that is: shared by multiple threads. + { + mMutexp.reset(new std::mutex()); + } +} + +LLVolatileAPRPool::~LLVolatileAPRPool() +{ + //delete mutex + mMutexp.reset(); +} + +// +//define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). +// +//virtual +apr_pool_t* LLVolatileAPRPool::getAPRPool() +{ + return LLVolatileAPRPool::getVolatileAPRPool() ; +} + +apr_pool_t* LLVolatileAPRPool::getVolatileAPRPool() +{ + LLScopedLock lock(mMutexp.get()) ; + + mNumTotalRef++ ; + mNumActiveRef++ ; + + if(!mPool) + { + createAPRPool() ; + } + + return mPool ; +} + +void LLVolatileAPRPool::clearVolatileAPRPool() +{ + LLScopedLock lock(mMutexp.get()); + + if(mNumActiveRef > 0) + { + mNumActiveRef--; + if(mNumActiveRef < 1) + { + if(isFull()) + { + mNumTotalRef = 0 ; + + //destroy the apr_pool. + releaseAPRPool() ; + } + else + { + //This does not actually free the memory, + //it just allows the pool to re-use this memory for the next allocation. + apr_pool_clear(mPool) ; + } + } + } + else + { + llassert_always(mNumActiveRef > 0) ; + } + + llassert(mNumTotalRef <= (FULL_VOLATILE_APR_POOL << 2)) ; +} + +bool LLVolatileAPRPool::isFull() +{ + return mNumTotalRef > FULL_VOLATILE_APR_POOL ; +} + +//--------------------------------------------------------------------- + +bool _ll_apr_warn_status(apr_status_t status, const char* file, int line) +{ + if(APR_SUCCESS == status) return false; +#if !LL_LINUX + char buf[MAX_STRING]; /* Flawfinder: ignore */ + apr_strerror(status, buf, sizeof(buf)); + LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL; +#endif + return true; +} + +void _ll_apr_assert_status(apr_status_t status, const char* file, int line) +{ + llassert(! _ll_apr_warn_status(status, file, line)); +} + +//--------------------------------------------------------------------- +// +// Scope based pool access +// +//--------------------------------------------------------------------- + +class LLAPRFilePoolScope +{ +public: + LLAPRFilePoolScope() : pPool(NULL), mInitialized(false) {} + LLAPRFilePoolScope(LLVolatileAPRPool* poolp) : mInitialized(false) + { + setFilePool(poolp); + } + ~LLAPRFilePoolScope() + { + reset(); + } + apr_pool_t* getVolatileAPRPool(LLVolatileAPRPool* poolp = NULL) + { + if (!pPool) + { + setFilePool(poolp); + } + if (mInitialized) + { + // We need one clear per one get + // At the moment no need to support multiple calls + LL_ERRS() << "LLAPRFilePoolScope is not supposed to be initialized twice" << LL_ENDL; + } + mInitialized = true; + return pPool->getVolatileAPRPool(); + } + void reset() + { + if (mInitialized) + { + pPool->clearVolatileAPRPool(); + } + } + +private: + void setFilePool(LLVolatileAPRPool* poolp = NULL) + { + if (poolp) + { + pPool = poolp; + } + else + { + pPool = LLAPRFile::sAPRFilePoolp; + } + } + + LLVolatileAPRPool *pPool; + bool mInitialized; +}; + +//--------------------------------------------------------------------- +// +// LLAPRFile functions +// +LLAPRFile::LLAPRFile() + : mFile(NULL), + mCurrentFilePoolp(NULL) +{ +} + +LLAPRFile::LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool) + : mFile(NULL), + mCurrentFilePoolp(NULL) +{ + open(filename, flags, pool); +} + +LLAPRFile::~LLAPRFile() +{ + close() ; +} + +apr_status_t LLAPRFile::close() +{ + apr_status_t ret = APR_SUCCESS ; + if(mFile) + { + ret = apr_file_close(mFile); + mFile = NULL ; + } + + if(mCurrentFilePoolp) + { + mCurrentFilePoolp->clearVolatileAPRPool() ; + mCurrentFilePoolp = NULL ; + } + + return ret ; +} + +apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool, S32* sizep) +{ + apr_status_t s ; + + //check if already open some file + llassert_always(!mFile) ; + llassert_always(!mCurrentFilePoolp) ; + + mCurrentFilePoolp = pool ? pool : sAPRFilePoolp; + apr_pool_t* apr_pool = mCurrentFilePoolp->getVolatileAPRPool(); //paired with clear in close() + s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); + + if (s != APR_SUCCESS || !mFile) + { + mFile = NULL ; + + if (sizep) + { + *sizep = 0; + } + } + else if (sizep) + { + S32 file_size = 0; + apr_off_t offset = 0; + if (apr_file_seek(mFile, APR_END, &offset) == APR_SUCCESS) + { + llassert_always(offset <= 0x7fffffff); + file_size = (S32)offset; + offset = 0; + apr_file_seek(mFile, APR_SET, &offset); + } + *sizep = file_size; + } + + if (!mFile) + { + // It will clean pool + close() ; + } + + return s ; +} + +//use gAPRPoolp. +apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, bool use_global_pool) +{ + apr_status_t s; + + //check if already open some file + llassert_always(!mFile) ; + llassert_always(!mCurrentFilePoolp) ; + llassert_always(use_global_pool) ; //be aware of using gAPRPoolp. + + s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, gAPRPoolp); + if (s != APR_SUCCESS || !mFile) + { + mFile = NULL ; + close() ; + return s; + } + + return s; +} + +// File I/O +S32 LLAPRFile::read(void *buf, S32 nbytes) +{ + if(!mFile) + { + LL_WARNS() << "apr mFile is removed by somebody else. Can not read." << LL_ENDL ; + return 0; + } + + apr_size_t sz = nbytes; + apr_status_t s = apr_file_read(mFile, buf, &sz); + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + return 0; + } + else + { + llassert_always(sz <= 0x7fffffff); + return (S32)sz; + } +} + +S32 LLAPRFile::write(const void *buf, S32 nbytes) +{ + if(!mFile) + { + LL_WARNS() << "apr mFile is removed by somebody else. Can not write." << LL_ENDL ; + return 0; + } + + apr_size_t sz = nbytes; + apr_status_t s = apr_file_write(mFile, buf, &sz); + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + return 0; + } + else + { + llassert_always(sz <= 0x7fffffff); + return (S32)sz; + } +} + +S32 LLAPRFile::seek(apr_seek_where_t where, S32 offset) +{ + return LLAPRFile::seek(mFile, where, offset) ; +} + +// +//******************************************************************************************************************************* +//static components of LLAPRFile +// + +//static +apr_status_t LLAPRFile::close(apr_file_t* file_handle) +{ + apr_status_t ret = APR_SUCCESS ; + if(file_handle) + { + ret = apr_file_close(file_handle); + file_handle = NULL ; + } + + return ret ; +} + +//static +apr_file_t* LLAPRFile::open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags) +{ + apr_status_t s; + apr_file_t* file_handle ; + + + s = apr_file_open(&file_handle, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); + if (s != APR_SUCCESS || !file_handle) + { + ll_apr_warn_status(s); + LL_WARNS("APR") << " Attempting to open filename: " << filename << LL_ENDL; + file_handle = NULL ; + close(file_handle) ; + return NULL; + } + + return file_handle ; +} + +//static +S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) +{ + if(!file_handle) + { + return -1 ; + } + + apr_status_t s; + apr_off_t apr_offset; + if (offset >= 0) + { + apr_offset = (apr_off_t)offset; + s = apr_file_seek(file_handle, where, &apr_offset); + } + else + { + apr_offset = 0; + s = apr_file_seek(file_handle, APR_END, &apr_offset); + } + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + return -1; + } + else + { + llassert_always(apr_offset <= 0x7fffffff); + return (S32)apr_offset; + } +} + +//static +S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) +{ + LL_PROFILE_ZONE_SCOPED; + //***************************************** + LLAPRFilePoolScope scope(pool); + apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY); + //***************************************** + if (!file_handle) + { + return 0; + } + + llassert(offset >= 0); + + if (offset > 0) + offset = LLAPRFile::seek(file_handle, APR_SET, offset); + + apr_size_t bytes_read; + if (offset < 0) + { + bytes_read = 0; + } + else + { + bytes_read = nbytes ; + apr_status_t s = apr_file_read(file_handle, buf, &bytes_read); + if (s != APR_SUCCESS) + { + LL_WARNS("APR") << " Attempting to read filename: " << filename << LL_ENDL; + ll_apr_warn_status(s); + bytes_read = 0; + } + else + { + llassert_always(bytes_read <= 0x7fffffff); + } + } + + //***************************************** + close(file_handle) ; + //***************************************** + return (S32)bytes_read; +} + +//static +S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) +{ + LL_PROFILE_ZONE_SCOPED; + apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; + if (offset < 0) + { + flags |= APR_APPEND; + offset = 0; + } + + //***************************************** + LLAPRFilePoolScope scope(pool); + apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), flags); + //***************************************** + if (!file_handle) + { + return 0; + } + + if (offset > 0) + { + offset = LLAPRFile::seek(file_handle, APR_SET, offset); + } + + apr_size_t bytes_written; + if (offset < 0) + { + bytes_written = 0; + } + else + { + bytes_written = nbytes ; + apr_status_t s = apr_file_write(file_handle, buf, &bytes_written); + if (s != APR_SUCCESS) + { + LL_WARNS("APR") << " Attempting to write filename: " << filename << LL_ENDL; + ll_apr_warn_status(s); + bytes_written = 0; + } + else + { + llassert_always(bytes_written <= 0x7fffffff); + } + } + + //***************************************** + LLAPRFile::close(file_handle); + //***************************************** + + return (S32)bytes_written; +} + +//static +bool LLAPRFile::remove(const std::string& filename, LLVolatileAPRPool* pool) +{ + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_file_remove(filename.c_str(), scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + LL_WARNS("APR") << " Attempting to remove filename: " << filename << LL_ENDL; + return false; + } + return true; +} + +//static +bool LLAPRFile::rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool) +{ + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_file_rename(filename.c_str(), newname.c_str(), scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + LL_WARNS("APR") << " Attempting to rename filename: " << filename << LL_ENDL; + return false; + } + return true; +} + +//static +bool LLAPRFile::isExist(const std::string& filename, LLVolatileAPRPool* pool, apr_int32_t flags) +{ + apr_file_t* apr_file; + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_file_open(&apr_file, filename.c_str(), flags, APR_OS_DEFAULT, scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS || !apr_file) + { + return false; + } + else + { + apr_file_close(apr_file) ; + return true; + } +} + +//static +S32 LLAPRFile::size(const std::string& filename, LLVolatileAPRPool* pool) +{ + apr_file_t* apr_file; + apr_finfo_t info; + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_file_open(&apr_file, filename.c_str(), APR_READ, APR_OS_DEFAULT, scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS || !apr_file) + { + return 0; + } + else + { + apr_status_t s = apr_file_info_get(&info, APR_FINFO_SIZE, apr_file); + + apr_file_close(apr_file) ; + + if (s == APR_SUCCESS) + { + return (S32)info.size; + } + else + { + return 0; + } + } +} + +//static +bool LLAPRFile::makeDir(const std::string& dirname, LLVolatileAPRPool* pool) +{ + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_dir_make(dirname.c_str(), APR_FPROT_OS_DEFAULT, scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + LL_WARNS("APR") << " Attempting to make directory: " << dirname << LL_ENDL; + return false; + } + return true; +} + +//static +bool LLAPRFile::removeDir(const std::string& dirname, LLVolatileAPRPool* pool) +{ + apr_status_t s; + + LLAPRFilePoolScope scope(pool); + s = apr_file_remove(dirname.c_str(), scope.getVolatileAPRPool()); + + if (s != APR_SUCCESS) + { + ll_apr_warn_status(s); + LL_WARNS("APR") << " Attempting to remove directory: " << dirname << LL_ENDL; + return false; + } + return true; +} +// +//end of static components of LLAPRFile +//******************************************************************************************************************************* +// diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 3a43339ca3..00ff4d60b7 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -1,201 +1,201 @@ -/** - * @file llapr.h - * @author Phoenix - * @date 2004-11-28 - * @brief Helper functions for using the apache portable runtime library. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAPR_H -#define LL_LLAPR_H - -#if LL_LINUX -#include // Need PATH_MAX in APR headers... -#endif - -#include -#include "llwin32headerslean.h" -#include "apr_thread_proc.h" -#include "apr_getopt.h" -#include "apr_signal.h" - -#include "llstring.h" - -#include "mutex.h" - -struct apr_dso_handle_t; -/** - * @brief Function which appropriately logs error or remains quiet on - * APR_SUCCESS. - * @return Returns true if status is an error condition. - */ -#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__) -bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line); - -#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__) -void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line); - -extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool - -/** - * @brief initialize the common apr constructs -- apr itself, the - * global pool, and a mutex. - */ -void LL_COMMON_API ll_init_apr(); - -/** - * @brief Cleanup those common apr constructs. - */ -void LL_COMMON_API ll_cleanup_apr(); - -bool LL_COMMON_API ll_apr_is_initialized(); - - -// -//LL apr_pool -//manage apr_pool_t, destroy allocated apr_pool in the destruction function. -// -class LL_COMMON_API LLAPRPool -{ -public: - LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ; - virtual ~LLAPRPool() ; - - virtual apr_pool_t* getAPRPool() ; - apr_status_t getStatus() {return mStatus ; } - -protected: - void releaseAPRPool() ; - void createAPRPool() ; - -protected: - apr_pool_t* mPool ; //pointing to an apr_pool - apr_pool_t* mParent ; //parent pool - apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work. - apr_status_t mStatus ; //status when creating the pool - bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. -}; - -// -//volatile LL apr_pool -//which clears memory automatically. -//so it can not hold static data or data after memory is cleared -// -class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool -{ -public: - LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true); - virtual ~LLVolatileAPRPool(); - - /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). - apr_pool_t* getVolatileAPRPool() ; - void clearVolatileAPRPool() ; - - bool isFull() ; - -private: - S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool. - S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating. - - std::unique_ptr mMutexp; -} ; - -// File IO convenience functions. -// Returns NULL if the file fails to open, sets *sizep to file size if not NULL -// abbreviated flags -#define LL_APR_R (APR_READ) // "r" -#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" -#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w" -#define LL_APR_RB (APR_READ|APR_BINARY) // "rb" -#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb" -#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND) -#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b" -#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b" - -// -//apr_file manager -//which: 1)only keeps one file open; -// 2)closes the open file in the destruction function -// 3)informs the apr_pool to clean the memory when the file is closed. -//Note: please close an open file at the earliest convenience. -// especially do not put some time-costly operations between open() and close(). -// otherwise it might lock the APRFilePool. -//there are two different apr_pools the APRFile can use: -// 1, a temporary pool passed to an APRFile function, which is used within this function and only once. -// 2, a global pool. -// - -class LL_COMMON_API LLAPRFile : boost::noncopyable -{ - // make this non copyable since a copy closes the file -private: - apr_file_t* mFile ; - LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool. - -public: - LLAPRFile() ; - LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL); - ~LLAPRFile() ; - - apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL); - apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp. - apr_status_t close() ; - - // Returns actual offset, -1 if seek fails - S32 seek(apr_seek_where_t where, S32 offset); - apr_status_t eof() { return apr_file_eof(mFile);} - - // Returns bytes read/written, 0 if read/write fails: - S32 read(void* buf, S32 nbytes); - S32 write(const void* buf, S32 nbytes); - - apr_file_t* getFileHandle() {return mFile;} - -// -//******************************************************************************************************************************* -//static components -// -public: - static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist. - -private: - static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags); - static apr_status_t close(apr_file_t* file) ; - static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset); -public: - // returns false if failure: - static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL); - static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ); - static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - - // Returns bytes read/written, 0 if read/write fails: - static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); - static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append -//******************************************************************************************************************************* -}; - - -#endif // LL_LLAPR_H +/** + * @file llapr.h + * @author Phoenix + * @date 2004-11-28 + * @brief Helper functions for using the apache portable runtime library. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAPR_H +#define LL_LLAPR_H + +#if LL_LINUX +#include // Need PATH_MAX in APR headers... +#endif + +#include +#include "llwin32headerslean.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_signal.h" + +#include "llstring.h" + +#include "mutex.h" + +struct apr_dso_handle_t; +/** + * @brief Function which appropriately logs error or remains quiet on + * APR_SUCCESS. + * @return Returns true if status is an error condition. + */ +#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__) +bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line); + +#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__) +void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line); + +extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool + +/** + * @brief initialize the common apr constructs -- apr itself, the + * global pool, and a mutex. + */ +void LL_COMMON_API ll_init_apr(); + +/** + * @brief Cleanup those common apr constructs. + */ +void LL_COMMON_API ll_cleanup_apr(); + +bool LL_COMMON_API ll_apr_is_initialized(); + + +// +//LL apr_pool +//manage apr_pool_t, destroy allocated apr_pool in the destruction function. +// +class LL_COMMON_API LLAPRPool +{ +public: + LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ; + virtual ~LLAPRPool() ; + + virtual apr_pool_t* getAPRPool() ; + apr_status_t getStatus() {return mStatus ; } + +protected: + void releaseAPRPool() ; + void createAPRPool() ; + +protected: + apr_pool_t* mPool ; //pointing to an apr_pool + apr_pool_t* mParent ; //parent pool + apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work. + apr_status_t mStatus ; //status when creating the pool + bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. +}; + +// +//volatile LL apr_pool +//which clears memory automatically. +//so it can not hold static data or data after memory is cleared +// +class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool +{ +public: + LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true); + virtual ~LLVolatileAPRPool(); + + /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). + apr_pool_t* getVolatileAPRPool() ; + void clearVolatileAPRPool() ; + + bool isFull() ; + +private: + S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool. + S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating. + + std::unique_ptr mMutexp; +} ; + +// File IO convenience functions. +// Returns NULL if the file fails to open, sets *sizep to file size if not NULL +// abbreviated flags +#define LL_APR_R (APR_READ) // "r" +#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" +#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w" +#define LL_APR_RB (APR_READ|APR_BINARY) // "rb" +#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb" +#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND) +#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b" +#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b" + +// +//apr_file manager +//which: 1)only keeps one file open; +// 2)closes the open file in the destruction function +// 3)informs the apr_pool to clean the memory when the file is closed. +//Note: please close an open file at the earliest convenience. +// especially do not put some time-costly operations between open() and close(). +// otherwise it might lock the APRFilePool. +//there are two different apr_pools the APRFile can use: +// 1, a temporary pool passed to an APRFile function, which is used within this function and only once. +// 2, a global pool. +// + +class LL_COMMON_API LLAPRFile : boost::noncopyable +{ + // make this non copyable since a copy closes the file +private: + apr_file_t* mFile ; + LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool. + +public: + LLAPRFile() ; + LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL); + ~LLAPRFile() ; + + apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL); + apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp. + apr_status_t close() ; + + // Returns actual offset, -1 if seek fails + S32 seek(apr_seek_where_t where, S32 offset); + apr_status_t eof() { return apr_file_eof(mFile);} + + // Returns bytes read/written, 0 if read/write fails: + S32 read(void* buf, S32 nbytes); + S32 write(const void* buf, S32 nbytes); + + apr_file_t* getFileHandle() {return mFile;} + +// +//******************************************************************************************************************************* +//static components +// +public: + static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist. + +private: + static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags); + static apr_status_t close(apr_file_t* file) ; + static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset); +public: + // returns false if failure: + static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL); + static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL); + static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ); + static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL); + static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); + static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); + + // Returns bytes read/written, 0 if read/write fails: + static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); + static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append +//******************************************************************************************************************************* +}; + + +#endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 3041c1f354..fe8510468a 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -1,246 +1,246 @@ -/** - * @file llassettype.cpp - * @brief Implementatino of LLAssetType functionality. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llassettype.h" -#include "lldictionary.h" -#include "llmemory.h" -#include "llsingleton.h" - -///---------------------------------------------------------------------------- -/// Class LLAssetType -///---------------------------------------------------------------------------- -struct AssetEntry : public LLDictionaryEntry -{ - AssetEntry(const char *desc_name, - const char *type_name, // 8 character limit! - const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one - bool can_link, // can you create a link to this type? - bool can_fetch, // can you fetch this asset by ID? - bool can_know) // can you see this asset's ID? - : - LLDictionaryEntry(desc_name), - mTypeName(type_name), - mHumanName(human_name), - mCanLink(can_link), - mCanFetch(can_fetch), - mCanKnow(can_know) - { - llassert(strlen(mTypeName) <= 8); - } - - const char *mTypeName; - const char *mHumanName; - bool mCanLink; - bool mCanFetch; - bool mCanKnow; -}; - -class LLAssetDictionary : public LLSingleton, - public LLDictionary -{ - LLSINGLETON(LLAssetDictionary); -}; - -LLAssetDictionary::LLAssetDictionary() -{ - // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW? - // |--------------------|-----------|-------------------|-----------|-----------|---------| - addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true)); - addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true)); - addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false)); - addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true)); - addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false)); - addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true)); - addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false)); - addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true)); - addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false)); - addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false)); - addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false)); - addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false)); - addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true)); - addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false)); - addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false)); - addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false)); - addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true)); - addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true)); - addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false)); - - addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true)); - addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true)); - addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false)); - addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); - addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); - addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); - addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); - addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); - addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false)); - -}; - -const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup"); - -// static -LLAssetType::EType LLAssetType::getType(const std::string& desc_name) -{ - std::string s = desc_name; - LLStringUtil::toUpper(s); - return LLAssetDictionary::getInstance()->lookup(s); -} - -// static -const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) -{ - const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type); - if (entry) - { - return entry->mName; - } - else - { - return BADLOOKUP; - } -} - -// static -const char *LLAssetType::lookup(LLAssetType::EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mTypeName; - } - else - { - return BADLOOKUP.c_str(); - } -} - -// static -LLAssetType::EType LLAssetType::lookup(const char* name) -{ - return lookup(ll_safe_string(name)); -} - -// static -LLAssetType::EType LLAssetType::lookup(const std::string& type_name) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - for (const LLAssetDictionary::value_type& pair : *dict) - { - const AssetEntry *entry = pair.second; - if (type_name == entry->mTypeName) - { - return pair.first; - } - } - return AT_UNKNOWN; -} - -// static -const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mHumanName; - } - else - { - return BADLOOKUP.c_str(); - } -} - -// static -LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) -{ - return lookupHumanReadable(ll_safe_string(name)); -} - -// static -LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - for (const LLAssetDictionary::value_type& pair : *dict) - { - const AssetEntry *entry = pair.second; - if (entry->mHumanName && (readable_name == entry->mHumanName)) - { - return pair.first; - } - } - return AT_NONE; -} - -// static -bool LLAssetType::lookupCanLink(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanLink; - } - return false; -} - -// static -// Not adding this to dictionary since we probably will only have these two types -bool LLAssetType::lookupIsLinkType(EType asset_type) -{ - if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER) - { - return true; - } - return false; -} - -// static -bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanFetch; - } - return false; -} - -// static -bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanKnow; - } - return false; -} +/** + * @file llassettype.cpp + * @brief Implementatino of LLAssetType functionality. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llassettype.h" +#include "lldictionary.h" +#include "llmemory.h" +#include "llsingleton.h" + +///---------------------------------------------------------------------------- +/// Class LLAssetType +///---------------------------------------------------------------------------- +struct AssetEntry : public LLDictionaryEntry +{ + AssetEntry(const char *desc_name, + const char *type_name, // 8 character limit! + const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one + bool can_link, // can you create a link to this type? + bool can_fetch, // can you fetch this asset by ID? + bool can_know) // can you see this asset's ID? + : + LLDictionaryEntry(desc_name), + mTypeName(type_name), + mHumanName(human_name), + mCanLink(can_link), + mCanFetch(can_fetch), + mCanKnow(can_know) + { + llassert(strlen(mTypeName) <= 8); + } + + const char *mTypeName; + const char *mHumanName; + bool mCanLink; + bool mCanFetch; + bool mCanKnow; +}; + +class LLAssetDictionary : public LLSingleton, + public LLDictionary +{ + LLSINGLETON(LLAssetDictionary); +}; + +LLAssetDictionary::LLAssetDictionary() +{ + // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW? + // |--------------------|-----------|-------------------|-----------|-----------|---------| + addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true)); + addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true)); + addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false)); + addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true)); + addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false)); + addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true)); + addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false)); + addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true)); + addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false)); + addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false)); + addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false)); + addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false)); + addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true)); + addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false)); + addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true)); + addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true)); + addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false)); + + addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true)); + addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true)); + addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false)); + addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); + addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); + addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); + addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); + addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); + addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false)); + +}; + +const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup"); + +// static +LLAssetType::EType LLAssetType::getType(const std::string& desc_name) +{ + std::string s = desc_name; + LLStringUtil::toUpper(s); + return LLAssetDictionary::getInstance()->lookup(s); +} + +// static +const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) +{ + const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type); + if (entry) + { + return entry->mName; + } + else + { + return BADLOOKUP; + } +} + +// static +const char *LLAssetType::lookup(LLAssetType::EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mTypeName; + } + else + { + return BADLOOKUP.c_str(); + } +} + +// static +LLAssetType::EType LLAssetType::lookup(const char* name) +{ + return lookup(ll_safe_string(name)); +} + +// static +LLAssetType::EType LLAssetType::lookup(const std::string& type_name) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (const LLAssetDictionary::value_type& pair : *dict) + { + const AssetEntry *entry = pair.second; + if (type_name == entry->mTypeName) + { + return pair.first; + } + } + return AT_UNKNOWN; +} + +// static +const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mHumanName; + } + else + { + return BADLOOKUP.c_str(); + } +} + +// static +LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) +{ + return lookupHumanReadable(ll_safe_string(name)); +} + +// static +LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (const LLAssetDictionary::value_type& pair : *dict) + { + const AssetEntry *entry = pair.second; + if (entry->mHumanName && (readable_name == entry->mHumanName)) + { + return pair.first; + } + } + return AT_NONE; +} + +// static +bool LLAssetType::lookupCanLink(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanLink; + } + return false; +} + +// static +// Not adding this to dictionary since we probably will only have these two types +bool LLAssetType::lookupIsLinkType(EType asset_type) +{ + if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER) + { + return true; + } + return false; +} + +// static +bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanFetch; + } + return false; +} + +// static +bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanKnow; + } + return false; +} diff --git a/indra/llcommon/llbase64.cpp b/indra/llcommon/llbase64.cpp index 144b878db3..b8185a0c84 100644 --- a/indra/llcommon/llbase64.cpp +++ b/indra/llcommon/llbase64.cpp @@ -1,77 +1,77 @@ -/** - * @file llbase64.cpp - * @brief Wrapper for apr base64 encoding that returns a std::string - * @author James Cook - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llbase64.h" - -#include - -#include "apr_base64.h" - - -// static -std::string LLBase64::encode(const U8* input, size_t input_size) -{ - std::string output; - if (input - && input_size > 0) - { - // Yes, it returns int. - int b64_buffer_length = apr_base64_encode_len(narrow(input_size)); - char* b64_buffer = new char[b64_buffer_length]; - - // This is faster than apr_base64_encode() if you know - // you're not on an EBCDIC machine. Also, the output is - // null terminated, even though the documentation doesn't - // specify. See apr_base64.c for details. JC - b64_buffer_length = apr_base64_encode_binary( - b64_buffer, - input, - narrow(input_size)); - output.assign(b64_buffer); - delete[] b64_buffer; - } - return output; -} - -std::string LLBase64::decodeAsString(const std::string &input) -{ - int b64_buffer_length = apr_base64_decode_len(input.c_str()); - char* b64_buffer = new char[b64_buffer_length]; - - // This is faster than apr_base64_encode() if you know - // you're not on an EBCDIC machine. Also, the output is - // null terminated, even though the documentation doesn't - // specify. See apr_base64.c for details. JC - b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str()); - std::string res; - res.assign(b64_buffer); - delete[] b64_buffer; - return res; -} - +/** + * @file llbase64.cpp + * @brief Wrapper for apr base64 encoding that returns a std::string + * @author James Cook + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llbase64.h" + +#include + +#include "apr_base64.h" + + +// static +std::string LLBase64::encode(const U8* input, size_t input_size) +{ + std::string output; + if (input + && input_size > 0) + { + // Yes, it returns int. + int b64_buffer_length = apr_base64_encode_len(narrow(input_size)); + char* b64_buffer = new char[b64_buffer_length]; + + // This is faster than apr_base64_encode() if you know + // you're not on an EBCDIC machine. Also, the output is + // null terminated, even though the documentation doesn't + // specify. See apr_base64.c for details. JC + b64_buffer_length = apr_base64_encode_binary( + b64_buffer, + input, + narrow(input_size)); + output.assign(b64_buffer); + delete[] b64_buffer; + } + return output; +} + +std::string LLBase64::decodeAsString(const std::string &input) +{ + int b64_buffer_length = apr_base64_decode_len(input.c_str()); + char* b64_buffer = new char[b64_buffer_length]; + + // This is faster than apr_base64_encode() if you know + // you're not on an EBCDIC machine. Also, the output is + // null terminated, even though the documentation doesn't + // specify. See apr_base64.c for details. JC + b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str()); + std::string res; + res.assign(b64_buffer); + delete[] b64_buffer; + return res; +} + diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index a64c4f1e54..4f21e65244 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -1,38 +1,38 @@ -/** - * @file llbase64.h - * @brief Wrapper for apr base64 encoding that returns a std::string - * @author James Cook - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLBASE64_H -#define LLBASE64_H - -class LL_COMMON_API LLBase64 -{ -public: - static std::string encode(const U8* input, size_t input_size); - static std::string decodeAsString(const std::string& input); -}; - -#endif +/** + * @file llbase64.h + * @brief Wrapper for apr base64 encoding that returns a std::string + * @author James Cook + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLBASE64_H +#define LLBASE64_H + +class LL_COMMON_API LLBase64 +{ +public: + static std::string encode(const U8* input, size_t input_size); + static std::string decodeAsString(const std::string& input); +}; + +#endif diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 9705b69e11..3d5d30bd90 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -1,230 +1,230 @@ -/** - * @file llcallbacklist.cpp - * @brief A simple list of callback functions to call. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llcallbacklist.h" -#include "lleventtimer.h" -#include "llerrorlegacy.h" - -// Globals -// -LLCallbackList gIdleCallbacks; - -// -// Member functions -// - -LLCallbackList::LLCallbackList() -{ - // nothing -} - -LLCallbackList::~LLCallbackList() -{ -} - - -void LLCallbackList::addFunction( callback_t func, void *data) -{ - if (!func) - { - return; - } - - // only add one callback per func/data pair - // - if (containsFunction(func, data)) - { - return; - } - - callback_pair_t t(func, data); - mCallbackList.push_back(t); -} - -bool LLCallbackList::containsFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) - { - return true; - } - else - { - return false; - } -} - - -bool LLCallbackList::deleteFunction( callback_t func, void *data) -{ - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) - { - mCallbackList.erase(iter); - return true; - } - else - { - return false; - } -} - -inline -LLCallbackList::callback_list_t::iterator -LLCallbackList::find(callback_t func, void *data) -{ - callback_pair_t t(func, data); - return std::find(mCallbackList.begin(), mCallbackList.end(), t); -} - -void LLCallbackList::deleteAllFunctions() -{ - mCallbackList.clear(); -} - - -void LLCallbackList::callFunctions() -{ - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); - } -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - -class NullaryFuncEventTimer: public LLEventTimer -{ -public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } - -private: - bool tick() - { - mCallable(); - return true; - } - - nullary_func_t mCallable; -}; - -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) -{ - new NullaryFuncEventTimer(callable, seconds); -} - -class BoolFuncEventTimer: public LLEventTimer -{ -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } -private: - bool tick() - { - return mCallable(); - } - - bool_func_t mCallable; -}; - -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} +/** + * @file llcallbacklist.cpp + * @brief A simple list of callback functions to call. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llcallbacklist.h" +#include "lleventtimer.h" +#include "llerrorlegacy.h" + +// Globals +// +LLCallbackList gIdleCallbacks; + +// +// Member functions +// + +LLCallbackList::LLCallbackList() +{ + // nothing +} + +LLCallbackList::~LLCallbackList() +{ +} + + +void LLCallbackList::addFunction( callback_t func, void *data) +{ + if (!func) + { + return; + } + + // only add one callback per func/data pair + // + if (containsFunction(func, data)) + { + return; + } + + callback_pair_t t(func, data); + mCallbackList.push_back(t); +} + +bool LLCallbackList::containsFunction( callback_t func, void *data) +{ + callback_pair_t t(func, data); + callback_list_t::iterator iter = find(func,data); + if (iter != mCallbackList.end()) + { + return true; + } + else + { + return false; + } +} + + +bool LLCallbackList::deleteFunction( callback_t func, void *data) +{ + callback_list_t::iterator iter = find(func,data); + if (iter != mCallbackList.end()) + { + mCallbackList.erase(iter); + return true; + } + else + { + return false; + } +} + +inline +LLCallbackList::callback_list_t::iterator +LLCallbackList::find(callback_t func, void *data) +{ + callback_pair_t t(func, data); + return std::find(mCallbackList.begin(), mCallbackList.end(), t); +} + +void LLCallbackList::deleteAllFunctions() +{ + mCallbackList.clear(); +} + + +void LLCallbackList::callFunctions() +{ + for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) + { + callback_list_t::iterator curiter = iter++; + curiter->first(curiter->second); + } +} + +// Shim class to allow arbitrary boost::bind +// expressions to be run as one-time idle callbacks. +class OnIdleCallbackOneTime +{ +public: + OnIdleCallbackOneTime(nullary_func_t callable): + mCallable(callable) + { + } + static void onIdle(void *data) + { + gIdleCallbacks.deleteFunction(onIdle, data); + OnIdleCallbackOneTime* self = reinterpret_cast(data); + self->call(); + delete self; + } + void call() + { + mCallable(); + } +private: + nullary_func_t mCallable; +}; + +void doOnIdleOneTime(nullary_func_t callable) +{ + OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); +} + +// Shim class to allow generic boost functions to be run as +// recurring idle callbacks. Callable should return true when done, +// false to continue getting called. +class OnIdleCallbackRepeating +{ +public: + OnIdleCallbackRepeating(bool_func_t callable): + mCallable(callable) + { + } + // Will keep getting called until the callable returns true. + static void onIdle(void *data) + { + OnIdleCallbackRepeating* self = reinterpret_cast(data); + bool done = self->call(); + if (done) + { + gIdleCallbacks.deleteFunction(onIdle, data); + delete self; + } + } + bool call() + { + return mCallable(); + } +private: + bool_func_t mCallable; +}; + +void doOnIdleRepeating(bool_func_t callable) +{ + OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); +} + +class NullaryFuncEventTimer: public LLEventTimer +{ +public: + NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } + +private: + bool tick() + { + mCallable(); + return true; + } + + nullary_func_t mCallable; +}; + +// Call a given callable once after specified interval. +void doAfterInterval(nullary_func_t callable, F32 seconds) +{ + new NullaryFuncEventTimer(callable, seconds); +} + +class BoolFuncEventTimer: public LLEventTimer +{ +public: + BoolFuncEventTimer(bool_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } +private: + bool tick() + { + return mCallable(); + } + + bool_func_t mCallable; +}; + +// Call a given callable every specified number of seconds, until it returns true. +void doPeriodically(bool_func_t callable, F32 seconds) +{ + new BoolFuncEventTimer(callable, seconds); +} diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 7a10662d3d..d22f26ff62 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -1,153 +1,153 @@ -/** - * @file llcommon.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llcommon.h" - -#include "llmemory.h" -#include "llthread.h" -#include "lltrace.h" -#include "lltracethreadrecorder.h" -#include "llcleanup.h" - -thread_local bool gProfilerEnabled = false; - -#if (TRACY_ENABLE) -// Override new/delete for tracy memory profiling - -void* ll_tracy_new(size_t size) -{ - void* ptr; - if (gProfilerEnabled) - { - //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - ptr = (malloc)(size); - } - else - { - ptr = (malloc)(size); - } - if (!ptr) - { - throw std::bad_alloc(); - } - TracyAlloc(ptr, size); - return ptr; -} - -void* operator new(size_t size) -{ - return ll_tracy_new(size); -} - -void* operator new[](std::size_t count) -{ - return ll_tracy_new(count); -} - -void ll_tracy_delete(void* ptr) -{ - TracyFree(ptr); - if (gProfilerEnabled) - { - //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - (free)(ptr); - } - else - { - (free)(ptr); - } -} - -void operator delete(void *ptr) noexcept -{ - ll_tracy_delete(ptr); -} - -void operator delete[](void* ptr) noexcept -{ - ll_tracy_delete(ptr); -} - -// C-style malloc/free can't be so easily overridden, so we define tracy versions and use -// a pre-processor #define in linden_common.h to redirect to them. The parens around the native -// functions below prevents recursive substitution by the preprocessor. -// -// Unaligned mallocs are rare in LL code but hooking them causes problems in 3p lib code (looking at -// you, Havok), so we'll only capture the aligned version. - -void *tracy_aligned_malloc(size_t size, size_t alignment) -{ - auto ptr = ll_aligned_malloc_fallback(size, alignment); - if (ptr) TracyAlloc(ptr, size); - return ptr; -} - -void tracy_aligned_free(void *memblock) -{ - TracyFree(memblock); - ll_aligned_free_fallback(memblock); -} - -#endif - -//static -bool LLCommon::sAprInitialized = false; - -static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; - -//static -void LLCommon::initClass() -{ - if (!sAprInitialized) - { - ll_init_apr(); - sAprInitialized = true; - } - LLTimer::initClass(); - LLThreadSafeRefCount::initThreadSafeRefCount(); - assert_main_thread(); // Make sure we record the main thread - if (!sMasterThreadRecorder) - { - sMasterThreadRecorder = new LLTrace::ThreadRecorder(); - LLTrace::set_master_thread_recorder(sMasterThreadRecorder); - } -} - -//static -void LLCommon::cleanupClass() -{ - delete sMasterThreadRecorder; - sMasterThreadRecorder = NULL; - LLTrace::set_master_thread_recorder(NULL); - LLThreadSafeRefCount::cleanupThreadSafeRefCount(); - SUBSYSTEM_CLEANUP_DBG(LLTimer); - if (sAprInitialized) - { - ll_cleanup_apr(); - sAprInitialized = false; - } -} +/** + * @file llcommon.cpp + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llcommon.h" + +#include "llmemory.h" +#include "llthread.h" +#include "lltrace.h" +#include "lltracethreadrecorder.h" +#include "llcleanup.h" + +thread_local bool gProfilerEnabled = false; + +#if (TRACY_ENABLE) +// Override new/delete for tracy memory profiling + +void* ll_tracy_new(size_t size) +{ + void* ptr; + if (gProfilerEnabled) + { + //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + ptr = (malloc)(size); + } + else + { + ptr = (malloc)(size); + } + if (!ptr) + { + throw std::bad_alloc(); + } + TracyAlloc(ptr, size); + return ptr; +} + +void* operator new(size_t size) +{ + return ll_tracy_new(size); +} + +void* operator new[](std::size_t count) +{ + return ll_tracy_new(count); +} + +void ll_tracy_delete(void* ptr) +{ + TracyFree(ptr); + if (gProfilerEnabled) + { + //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + (free)(ptr); + } + else + { + (free)(ptr); + } +} + +void operator delete(void *ptr) noexcept +{ + ll_tracy_delete(ptr); +} + +void operator delete[](void* ptr) noexcept +{ + ll_tracy_delete(ptr); +} + +// C-style malloc/free can't be so easily overridden, so we define tracy versions and use +// a pre-processor #define in linden_common.h to redirect to them. The parens around the native +// functions below prevents recursive substitution by the preprocessor. +// +// Unaligned mallocs are rare in LL code but hooking them causes problems in 3p lib code (looking at +// you, Havok), so we'll only capture the aligned version. + +void *tracy_aligned_malloc(size_t size, size_t alignment) +{ + auto ptr = ll_aligned_malloc_fallback(size, alignment); + if (ptr) TracyAlloc(ptr, size); + return ptr; +} + +void tracy_aligned_free(void *memblock) +{ + TracyFree(memblock); + ll_aligned_free_fallback(memblock); +} + +#endif + +//static +bool LLCommon::sAprInitialized = false; + +static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; + +//static +void LLCommon::initClass() +{ + if (!sAprInitialized) + { + ll_init_apr(); + sAprInitialized = true; + } + LLTimer::initClass(); + LLThreadSafeRefCount::initThreadSafeRefCount(); + assert_main_thread(); // Make sure we record the main thread + if (!sMasterThreadRecorder) + { + sMasterThreadRecorder = new LLTrace::ThreadRecorder(); + LLTrace::set_master_thread_recorder(sMasterThreadRecorder); + } +} + +//static +void LLCommon::cleanupClass() +{ + delete sMasterThreadRecorder; + sMasterThreadRecorder = NULL; + LLTrace::set_master_thread_recorder(NULL); + LLThreadSafeRefCount::cleanupThreadSafeRefCount(); + SUBSYSTEM_CLEANUP_DBG(LLTimer); + if (sAprInitialized) + { + ll_cleanup_apr(); + sAprInitialized = false; + } +} diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index 3e81bb1f79..41a101eb62 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -1,43 +1,43 @@ -/** - * @file llcommon.h - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_COMMON_H -#define LL_COMMON_H - -// *TODO: remove these? -#include "lltimer.h" -#include "llfile.h" - -class LL_COMMON_API LLCommon -{ -public: - static void initClass(); - static void cleanupClass(); -private: - static bool sAprInitialized; -}; - -#endif - +/** + * @file llcommon.h + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_COMMON_H +#define LL_COMMON_H + +// *TODO: remove these? +#include "lltimer.h" +#include "llfile.h" + +class LL_COMMON_API LLCommon +{ +public: + static void initClass(); + static void cleanupClass(); +private: + static bool sAprInitialized; +}; + +#endif + diff --git a/indra/llcommon/llcrc.cpp b/indra/llcommon/llcrc.cpp index c376269981..d79d06e2a2 100644 --- a/indra/llcommon/llcrc.cpp +++ b/indra/llcommon/llcrc.cpp @@ -1,223 +1,223 @@ -/** - * @file llcrc.cpp - * @brief implementation of the crc class. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llcrc.h" -#include "llerror.h" - -/* Copyright (C) 1986 Gary S. Brown. You may use this program, or - code or tables extracted from it, as desired without restriction.*/ - -/* First, the polynomial itself and its table of feedback terms. The */ -/* polynomial is */ -/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ -/* Note that we take it "backwards" and put the highest-order term in */ -/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ -/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ -/* the MSB being 1. */ - -/* Note that the usual hardware shift register implementation, which */ -/* is what we're using (we're merely optimizing it by doing eight-bit */ -/* chunks at a time) shifts bits into the lowest-order term. In our */ -/* implementation, that means shifting towards the right. Why do we */ -/* do it this way? Because the calculated CRC must be transmitted in */ -/* order from highest-order term to lowest-order term. UARTs transmit */ -/* characters in order from LSB to MSB. By storing the CRC this way, */ -/* we hand it to the UART in the order low-byte to high-byte; the UART */ -/* sends each low-bit to hight-bit; and the result is transmission bit */ -/* by bit from highest- to lowest-order term without requiring any bit */ -/* shuffling on our part. Reception works similarly. */ - -/* The feedback terms table consists of 256, 32-bit entries. Notes: */ -/* */ -/* 1. The table can be generated at runtime if desired; code to do so */ -/* is shown later. It might not be obvious, but the feedback */ -/* terms simply represent the results of eight shift/xor opera- */ -/* tions for all combinations of data and CRC register values. */ -/* */ -/* 2. The CRC accumulation logic is the same for all CRC polynomials, */ -/* be they sixteen or thirty-two bits wide. You simply choose the */ -/* appropriate table. Alternatively, because the table can be */ -/* generated at runtime, you can start by generating the table for */ -/* the polynomial in question and use exactly the same "updcrc", */ -/* if your application needn't simultaneously handle two CRC */ -/* polynomials. (Note, however, that XMODEM is strange.) */ -/* */ -/* 3. For 16-bit CRCs, the table entries need be only 16 bits wide; */ -/* of course, 32-bit entries work OK if the high 16 bits are zero. */ -/* */ -/* 4. The values must be right-shifted by eight bits by the "updcrc" */ -/* logic; the shift must be unsigned (bring in zeroes). On some */ -/* hardware you could probably optimize the shift in assembler by */ -/* using byte-swap instructions. */ - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -#define UPDC32(octet,crc) (crc_32_tab[((crc) \ - ^ ((U8)octet)) & 0xff] ^ ((crc) >> 8)) - - -static U32 crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ -0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, -0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, -0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, -0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, -0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, -0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, -0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, -0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, -0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, -0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, -0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, -0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, -0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, -0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, -0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, -0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, -0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, -0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, -0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, -0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, -0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, -0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, -0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, -0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, -0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, -0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, -0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, -0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, -0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, -0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, -0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, -0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, -0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, -0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, -0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, -0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, -0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, -0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, -0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, -0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, -0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, -0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, -0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -}; - - -///---------------------------------------------------------------------------- -/// Class llcrc -///---------------------------------------------------------------------------- - -// Default constructor -LLCRC::LLCRC() : mCurrent(0xffffffff) -{ -} - - -U32 LLCRC::getCRC() const -{ - return ~mCurrent; -} - -void LLCRC::update(U8 next_byte) -{ - mCurrent = UPDC32(next_byte, mCurrent); -} - -void LLCRC::update(const U8* buffer, size_t buffer_size) -{ - for (size_t i = 0; i < buffer_size; i++) - { - mCurrent = UPDC32(buffer[i], mCurrent); - } -} - -void LLCRC::update(const std::string& filename) -{ - if (filename.empty()) - { - LL_ERRS() << "No filename specified" << LL_ENDL; - return; - } - - FILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ - - if (fp) - { - fseek(fp, 0, SEEK_END); - long size = ftell(fp); - - fseek(fp, 0, SEEK_SET); - - if (size > 0) - { - U8* data = new U8[size]; - size_t nread; - - nread = fread(data, 1, size, fp); - fclose(fp); - - if (nread < (size_t) size) - { - LL_WARNS() << "Short read on " << filename << LL_ENDL; - } - - update(data, nread); - delete[] data; - } - else - { - fclose(fp); - } - } -} - - -#ifdef _DEBUG -bool LLCRC::testHarness() -{ - const S32 TEST_BUFFER_SIZE = 16; - const char TEST_BUFFER[TEST_BUFFER_SIZE] = "hello &#$)$&Nd0"; /* Flawfinder: ignore */ - LLCRC c1, c2; - c1.update((U8*)TEST_BUFFER, TEST_BUFFER_SIZE - 1); - char* rh = (char*)TEST_BUFFER; - while(*rh != '\0') - { - c2.update(*rh); - ++rh; - } - return(c1.getCRC() == c2.getCRC()); -} -#endif - - - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- +/** + * @file llcrc.cpp + * @brief implementation of the crc class. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llcrc.h" +#include "llerror.h" + +/* Copyright (C) 1986 Gary S. Brown. You may use this program, or + code or tables extracted from it, as desired without restriction.*/ + +/* First, the polynomial itself and its table of feedback terms. The */ +/* polynomial is */ +/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ +/* Note that we take it "backwards" and put the highest-order term in */ +/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ +/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ +/* the MSB being 1. */ + +/* Note that the usual hardware shift register implementation, which */ +/* is what we're using (we're merely optimizing it by doing eight-bit */ +/* chunks at a time) shifts bits into the lowest-order term. In our */ +/* implementation, that means shifting towards the right. Why do we */ +/* do it this way? Because the calculated CRC must be transmitted in */ +/* order from highest-order term to lowest-order term. UARTs transmit */ +/* characters in order from LSB to MSB. By storing the CRC this way, */ +/* we hand it to the UART in the order low-byte to high-byte; the UART */ +/* sends each low-bit to hight-bit; and the result is transmission bit */ +/* by bit from highest- to lowest-order term without requiring any bit */ +/* shuffling on our part. Reception works similarly. */ + +/* The feedback terms table consists of 256, 32-bit entries. Notes: */ +/* */ +/* 1. The table can be generated at runtime if desired; code to do so */ +/* is shown later. It might not be obvious, but the feedback */ +/* terms simply represent the results of eight shift/xor opera- */ +/* tions for all combinations of data and CRC register values. */ +/* */ +/* 2. The CRC accumulation logic is the same for all CRC polynomials, */ +/* be they sixteen or thirty-two bits wide. You simply choose the */ +/* appropriate table. Alternatively, because the table can be */ +/* generated at runtime, you can start by generating the table for */ +/* the polynomial in question and use exactly the same "updcrc", */ +/* if your application needn't simultaneously handle two CRC */ +/* polynomials. (Note, however, that XMODEM is strange.) */ +/* */ +/* 3. For 16-bit CRCs, the table entries need be only 16 bits wide; */ +/* of course, 32-bit entries work OK if the high 16 bits are zero. */ +/* */ +/* 4. The values must be right-shifted by eight bits by the "updcrc" */ +/* logic; the shift must be unsigned (bring in zeroes). On some */ +/* hardware you could probably optimize the shift in assembler by */ +/* using byte-swap instructions. */ + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +#define UPDC32(octet,crc) (crc_32_tab[((crc) \ + ^ ((U8)octet)) & 0xff] ^ ((crc) >> 8)) + + +static U32 crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ +0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, +0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, +0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, +0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, +0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, +0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, +0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, +0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, +0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, +0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, +0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, +0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, +0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, +0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, +0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, +0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, +0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, +0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, +0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, +0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, +0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, +0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, +0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, +0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, +0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, +0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, +0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, +0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, +0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, +0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, +0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, +0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, +0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, +0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, +0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, +0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, +0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, +0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, +0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, +0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, +0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, +0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, +0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + + +///---------------------------------------------------------------------------- +/// Class llcrc +///---------------------------------------------------------------------------- + +// Default constructor +LLCRC::LLCRC() : mCurrent(0xffffffff) +{ +} + + +U32 LLCRC::getCRC() const +{ + return ~mCurrent; +} + +void LLCRC::update(U8 next_byte) +{ + mCurrent = UPDC32(next_byte, mCurrent); +} + +void LLCRC::update(const U8* buffer, size_t buffer_size) +{ + for (size_t i = 0; i < buffer_size; i++) + { + mCurrent = UPDC32(buffer[i], mCurrent); + } +} + +void LLCRC::update(const std::string& filename) +{ + if (filename.empty()) + { + LL_ERRS() << "No filename specified" << LL_ENDL; + return; + } + + FILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ + + if (fp) + { + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + + fseek(fp, 0, SEEK_SET); + + if (size > 0) + { + U8* data = new U8[size]; + size_t nread; + + nread = fread(data, 1, size, fp); + fclose(fp); + + if (nread < (size_t) size) + { + LL_WARNS() << "Short read on " << filename << LL_ENDL; + } + + update(data, nread); + delete[] data; + } + else + { + fclose(fp); + } + } +} + + +#ifdef _DEBUG +bool LLCRC::testHarness() +{ + const S32 TEST_BUFFER_SIZE = 16; + const char TEST_BUFFER[TEST_BUFFER_SIZE] = "hello &#$)$&Nd0"; /* Flawfinder: ignore */ + LLCRC c1, c2; + c1.update((U8*)TEST_BUFFER, TEST_BUFFER_SIZE - 1); + char* rh = (char*)TEST_BUFFER; + while(*rh != '\0') + { + c2.update(*rh); + ++rh; + } + return(c1.getCRC() == c2.getCRC()); +} +#endif + + + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index d6fd008740..a3bde47780 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -1,68 +1,68 @@ -/** - * @file llcrc.h - * @brief LLCRC class header file. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCRC_H -#define LL_LLCRC_H - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class llcrc -// -// Simple 32 bit crc. To use, instantiate an LLCRC instance and feed -// it the bytes you want to check. It will update the internal crc as -// you go, and you can qery it at the end. As a horribly inefficient -// example (don't try this at work kids): -// -// LLCRC crc; -// FILE* fp = LLFile::fopen(filename,"rb"); -// while(!feof(fp)) { -// crc.update(fgetc(fp)); -// } -// fclose(fp); -// LL_INFOS() << "File crc: " << crc.getCRC() << LL_ENDL; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LL_COMMON_API LLCRC -{ -protected: - U32 mCurrent; - -public: - LLCRC(); - - U32 getCRC() const; - void update(U8 next_byte); - void update(const U8* buffer, size_t buffer_size); - void update(const std::string& filename); - -#ifdef _DEBUG - // This function runs tests to make sure the crc is - // working. Returns true if it is. - static bool testHarness(); -#endif -}; - - -#endif // LL_LLCRC_H +/** + * @file llcrc.h + * @brief LLCRC class header file. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCRC_H +#define LL_LLCRC_H + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class llcrc +// +// Simple 32 bit crc. To use, instantiate an LLCRC instance and feed +// it the bytes you want to check. It will update the internal crc as +// you go, and you can qery it at the end. As a horribly inefficient +// example (don't try this at work kids): +// +// LLCRC crc; +// FILE* fp = LLFile::fopen(filename,"rb"); +// while(!feof(fp)) { +// crc.update(fgetc(fp)); +// } +// fclose(fp); +// LL_INFOS() << "File crc: " << crc.getCRC() << LL_ENDL; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LL_COMMON_API LLCRC +{ +protected: + U32 mCurrent; + +public: + LLCRC(); + + U32 getCRC() const; + void update(U8 next_byte); + void update(const U8* buffer, size_t buffer_size); + void update(const std::string& filename); + +#ifdef _DEBUG + // This function runs tests to make sure the crc is + // working. Returns true if it is. + static bool testHarness(); +#endif +}; + + +#endif // LL_LLCRC_H diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 80fcaefad2..94aee26df6 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1,1662 +1,1662 @@ -/** - * @file llerror.cpp - * @date December 2006 - * @brief error message system - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llerror.h" -#include "llerrorcontrol.h" -#include "llsdutil.h" - -#include -#ifdef __GNUC__ -# include -#endif // __GNUC__ -#include -#if !LL_WINDOWS -# include -# include -# include -#else -# include -#endif // !LL_WINDOWS -#include -#include "string.h" - -#include "llapp.h" -#include "llapr.h" -#include "llfile.h" -#include "lllivefile.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llsingleton.h" -#include "llstl.h" -#include "lltimer.h" - -// On Mac, got: -// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define -// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if -// _Unwind_Backtrace is available without `_GNU_SOURCE`." -#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED -#include - -namespace { -#if LL_WINDOWS - void debugger_print(const std::string& s) - { - // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C - // which works just fine under the windows debugger, but can cause users who - // have enabled SEHOP exception chain validation to crash due to interactions - // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707 - // - if (IsDebuggerPresent()) - { - // Need UTF16 for Unicode OutputDebugString - // - if (s.size()) - { - OutputDebugString(utf8str_to_utf16str(s).c_str()); - OutputDebugString(TEXT("\n")); - } - } - } -#else - class RecordToSyslog : public LLError::Recorder - { - public: - RecordToSyslog(const std::string& identity) - : mIdentity(identity) - { - openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); - // we need to set the string from a local copy of the string - // since apparanetly openlog expects the const char* to remain - // valid even after it returns (presumably until closelog) - } - - ~RecordToSyslog() - { - closelog(); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x01; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - int syslogPriority = LOG_CRIT; - switch (level) { - case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; - case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; - case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; - case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; - default: syslogPriority = LOG_CRIT; - } - - syslog(syslogPriority, "%s", message.c_str()); - } - private: - std::string mIdentity; - }; -#endif - - class RecordToFile : public LLError::Recorder - { - public: - RecordToFile(const std::string& filename): - mName(filename) - { - mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app); - if (!mFile) - { - LL_INFOS() << "Error setting log file to " << filename << LL_ENDL; - } - else - { - if (!LLError::getAlwaysFlush()) - { - mFile.sync_with_stdio(false); - } - } - } - - ~RecordToFile() - { - mFile.close(); - } - - virtual bool enabled() override - { -#ifdef LL_RELEASE_FOR_DOWNLOAD - return 1; -#else - return LLError::getEnabledLogTypesMask() & 0x02; -#endif - } - - bool okay() const { return mFile.good(); } - - std::string getFilename() const { return mName; } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - if (LLError::getAlwaysFlush()) - { - mFile << message << std::endl; - } - else - { - mFile << message << "\n"; - } - } - - private: - const std::string mName; - llofstream mFile; - }; - - - class RecordToStderr : public LLError::Recorder - { - public: - RecordToStderr(bool timestamp) : mUseANSI(checkANSI()) - { - this->showMultiline(true); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x04; - } - - LL_FORCE_INLINE std::string createBoldANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "1"; - ansi_code += "m"; - - return ansi_code; - } - - LL_FORCE_INLINE std::string createResetANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "0"; - ansi_code += "m"; - - return ansi_code; - } - - LL_FORCE_INLINE std::string createANSI(const std::string& color) - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "38;5;"; - ansi_code += color; - ansi_code += "m"; - - return ansi_code; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - // The default colors for error, warn and debug are now a bit more pastel - // and easier to read on the default (black) terminal background but you - // now have the option to set the color of each via an environment variables: - // LL_ANSI_ERROR_COLOR_CODE (default is red) - // LL_ANSI_WARN_COLOR_CODE (default is blue) - // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) - // The list of color codes can be found in many places but I used this page: - // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors - // (Note: you may need to restart Visual Studio to pick environment changes) - char* val = nullptr; - std::string s_ansi_error_code = "160"; - if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); - std::string s_ansi_warn_code = "33"; - if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); - std::string s_ansi_debug_code = "177"; - if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); - - static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red - static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue - static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta - - if (mUseANSI) - { - writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error : - (level == LLError::LEVEL_WARN) ? s_ansi_warn : - s_ansi_debug, message); - } - else - { - LL_PROFILE_ZONE_NAMED("fprintf"); - fprintf(stderr, "%s\n", message.c_str()); - } - } - - private: - bool mUseANSI; - - LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - static std::string s_ansi_bold = createBoldANSI(); // bold text - static std::string s_ansi_reset = createResetANSI(); // reset - // ANSI color code escape sequence, message, and reset in one fprintf call - // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. - fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); - } - - static bool checkANSI(void) - { - // Check whether it's okay to use ANSI; if stderr is - // a tty then we assume yes. Can be turned off with - // the LL_NO_ANSI_COLOR env var. - return (0 != isatty(2)) && - (NULL == getenv("LL_NO_ANSI_COLOR")); - } - }; - - class RecordToFixedBuffer : public LLError::Recorder - { - public: - RecordToFixedBuffer(LLLineBuffer* buffer) - : mBuffer(buffer) - { - this->showMultiline(true); - this->showTags(false); - this->showLocation(false); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x08; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - mBuffer->addLine(message); - } - - private: - LLLineBuffer* mBuffer; - }; - -#if LL_WINDOWS - class RecordToWinDebug: public LLError::Recorder - { - public: - RecordToWinDebug() - { - this->showMultiline(true); - this->showTags(false); - this->showLocation(false); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x10; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - debugger_print(message); - } - }; -#endif -} - - -namespace -{ - std::string className(const std::type_info& type) - { - return LLError::Log::demangle(type.name()); - } -} // anonymous - -namespace LLError -{ - std::string Log::demangle(const char* mangled) - { -#ifdef __GNUC__ - // GCC: type_info::name() returns a mangled class name,st demangle - // passing nullptr, 0 forces allocation of a unique buffer we can free - // fixing MAINT-8724 on OSX 10.14 - int status = -1; - char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status); - std::string result(name ? name : mangled); - free(name); - return result; - -#elif LL_WINDOWS - // Visual Studio: type_info::name() includes the text "class " at the start - std::string name = mangled; - for (const auto& prefix : std::vector{ "class ", "struct " }) - { - if (0 == name.compare(0, prefix.length(), prefix)) - { - return name.substr(prefix.length()); - } - } - // huh, that's odd, we should see one or the other prefix -- but don't - // try to log unless logging is already initialized - // in Python, " or ".join(vector) -- but in C++, a PITB - LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" - << name << "'" << LL_ENDL; - return name; - -#else // neither GCC nor Visual Studio - return mangled; -#endif - } -} // LLError - -namespace -{ - std::string functionName(const std::string& preprocessor_name) - { -#if LL_WINDOWS - // DevStudio: the __FUNCTION__ macro string includes - // the type and/or namespace prefixes - - std::string::size_type p = preprocessor_name.rfind(':'); - if (p == std::string::npos) - { - return preprocessor_name; - } - return preprocessor_name.substr(p + 1); - -#else - return preprocessor_name; -#endif - } - - - class LogControlFile : public LLLiveFile - { - LOG_CLASS(LogControlFile); - - public: - static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir); - - virtual bool loadFile(); - - private: - LogControlFile(const std::string &filename) - : LLLiveFile(filename) - { } - }; - - LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir) - { - // NB: We have no abstraction in llcommon for the "proper" - // delimiter but it turns out that "/" works on all three platforms - - std::string file = user_dir + "/logcontrol-dev.xml"; - - llstat stat_info; - if (LLFile::stat(file, &stat_info)) { - // NB: stat returns non-zero if it can't read the file, for example - // if it doesn't exist. LLFile has no better abstraction for - // testing for file existence. - - file = app_dir + "/logcontrol.xml"; - } - return * new LogControlFile(file); - // NB: This instance is never freed - } - - bool LogControlFile::loadFile() - { - LLSD configuration; - - { - llifstream file(filename().c_str()); - if (!file.is_open()) - { - LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL; - return false; - } - - if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE) - { - LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL; - return false; - } - - if (! configuration || !configuration.isMap()) - { - LL_WARNS() << filename() << " missing, ill-formed, or simply undefined" - " content; not changing configuration" - << LL_ENDL; - return false; - } - } - - LLError::configure(configuration); - LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL; - return true; - } - - - typedef std::map LevelMap; - typedef std::vector Recorders; - typedef std::vector CallSiteVector; - - class SettingsConfig : public LLRefCount - { - friend class Globals; - - public: - virtual ~SettingsConfig(); - - LLError::ELevel mDefaultLevel; - - bool mLogAlwaysFlush; - - U32 mEnabledLogTypesMask; - - LevelMap mFunctionLevelMap; - LevelMap mClassLevelMap; - LevelMap mFileLevelMap; - LevelMap mTagLevelMap; - std::map mUniqueLogMessages; - - LLError::FatalFunction mCrashFunction; - LLError::TimeFunction mTimeFunction; - - Recorders mRecorders; - LLMutex mRecorderMutex; - - int mShouldLogCallCounter; - - private: - SettingsConfig(); - }; - - typedef LLPointer SettingsConfigPtr; - - SettingsConfig::SettingsConfig() - : LLRefCount(), - mDefaultLevel(LLError::LEVEL_DEBUG), - mLogAlwaysFlush(true), - mEnabledLogTypesMask(255), - mFunctionLevelMap(), - mClassLevelMap(), - mFileLevelMap(), - mTagLevelMap(), - mUniqueLogMessages(), - mCrashFunction(NULL), - mTimeFunction(NULL), - mRecorders(), - mRecorderMutex(), - mShouldLogCallCounter(0) - { - } - - SettingsConfig::~SettingsConfig() - { - mRecorders.clear(); - } - - class Globals - { - public: - static Globals* getInstance(); - protected: - Globals(); - public: - std::string mFatalMessage; - - void addCallSite(LLError::CallSite&); - void invalidateCallSites(); - - SettingsConfigPtr getSettingsConfig(); - - void resetSettingsConfig(); - LLError::SettingsStoragePtr saveAndResetSettingsConfig(); - void restore(LLError::SettingsStoragePtr pSettingsStorage); - private: - CallSiteVector callSites; - SettingsConfigPtr mSettingsConfig; - }; - - Globals::Globals() - : - callSites(), - mSettingsConfig(new SettingsConfig()) - { - } - - - Globals* Globals::getInstance() - { - // According to C++11 Function-Local Initialization - // of static variables is supposed to be thread safe - // without risk of deadlocks. - static Globals inst; - - return &inst; - } - - void Globals::addCallSite(LLError::CallSite& site) - { - callSites.push_back(&site); - } - - void Globals::invalidateCallSites() - { - for (LLError::CallSite* site : callSites) - { - site->invalidate(); - } - - callSites.clear(); - } - - SettingsConfigPtr Globals::getSettingsConfig() - { - return mSettingsConfig; - } - - void Globals::resetSettingsConfig() - { - invalidateCallSites(); - mSettingsConfig = new SettingsConfig(); - } - - LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig() - { - LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get()); - resetSettingsConfig(); - return oldSettingsConfig; - } - - void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage) - { - invalidateCallSites(); - SettingsConfigPtr newSettingsConfig(dynamic_cast(pSettingsStorage.get())); - mSettingsConfig = newSettingsConfig; - } -} - -namespace LLError -{ - CallSite::CallSite(ELevel level, - const char* file, - int line, - const std::type_info& class_info, - const char* function, - bool printOnce, - const char** tags, - size_t tag_count) - : mLevel(level), - mFile(file), - mLine(line), - mClassInfo(class_info), - mFunction(function), - mCached(false), - mShouldLog(false), - mPrintOnce(printOnce), - mTags(new const char* [tag_count]), - mTagCount(tag_count) - { - switch (mLevel) - { - case LEVEL_DEBUG: mLevelString = "DEBUG"; break; - case LEVEL_INFO: mLevelString = "INFO"; break; - case LEVEL_WARN: mLevelString = "WARNING"; break; - case LEVEL_ERROR: mLevelString = "ERROR"; break; - default: mLevelString = "XXX"; break; - }; - - mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine); -#if LL_WINDOWS - // DevStudio: __FUNCTION__ already includes the full class name -#else -#if LL_LINUX - // gross, but typeid comparison seems to always fail here with gcc4.1 - if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name())) -#else - if (mClassInfo != typeid(NoClassInfo)) -#endif // LL_LINUX - { - mFunctionString = className(mClassInfo) + "::"; - } -#endif - mFunctionString += std::string(mFunction); - - for (int i = 0; i < tag_count; i++) - { - if (strchr(tags[i], ' ')) - { - LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL; - } - mTags[i] = tags[i]; - } - - mTagString.append("#"); - // always construct a tag sequence; will be just a single # if no tag - for (size_t i = 0; i < mTagCount; i++) - { - mTagString.append(mTags[i]); - mTagString.append("#"); - } - } - - CallSite::~CallSite() - { - delete []mTags; - } - - void CallSite::invalidate() - { - mCached = false; - } -} - -namespace -{ - bool shouldLogToStderr() - { -#if LL_DARWIN - // On macOS, stderr from apps launched from the Finder goes to the - // console log. It's generally considered bad form to spam too much - // there. That scenario can be detected by noticing that stderr is a - // character device (S_IFCHR). - - // If stderr is a tty or a pipe, assume the user launched from the - // command line or debugger and therefore wants to see stderr. - if (isatty(STDERR_FILENO)) - return true; - // not a tty, but might still be a pipe -- check - struct stat st; - if (fstat(STDERR_FILENO, &st) < 0) - { - // capture errno right away, before engaging any other operations - auto errno_save = errno; - // this gets called during log-system setup -- can't log yet! - std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno " - << errno_save << std::endl; - // if we can't tell, err on the safe side and don't write stderr - return false; - } - - // fstat() worked: return true only if stderr is a pipe - return ((st.st_mode & S_IFMT) == S_IFIFO); -#else - return true; -#endif - } - - bool stderrLogWantsTime() - { -#if LL_WINDOWS - return false; -#else - return true; -#endif - } - - - void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true) - { - Globals::getInstance()->resetSettingsConfig(); - - LLError::setDefaultLevel(LLError::LEVEL_INFO); - LLError::setAlwaysFlush(true); - LLError::setEnabledLogTypesMask(0xFFFFFFFF); - LLError::setTimeFunction(LLError::utcTime); - - // log_to_stderr is only false in the unit and integration tests to keep builds quieter - if (log_to_stderr && shouldLogToStderr()) - { - LLError::logToStderr(); - } - -#if LL_WINDOWS - LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug()); - LLError::addRecorder(recordToWinDebug); -#endif - - LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir); - - // NOTE: We want to explicitly load the file before we add it to the event timer - // that checks for changes to the file. Else, we're not actually loading the file yet, - // and most of the initialization happens without any attention being paid to the - // log control file. Not to mention that when it finally gets checked later, - // all log statements that have been evaluated already become dirty and need to be - // evaluated for printing again. So, make sure to call checkAndReload() - // before addToEventTimer(). - e.checkAndReload(); - e.addToEventTimer(); - } -} - -namespace LLError -{ - void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr) - { - commonInit(user_dir, app_dir, log_to_stderr); - } - - void setFatalFunction(const FatalFunction& f) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mCrashFunction = f; - } - - FatalFunction getFatalFunction() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mCrashFunction; - } - - std::string getFatalMessage() - { - return Globals::getInstance()->mFatalMessage; - } - - void setTimeFunction(TimeFunction f) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mTimeFunction = f; - } - - void setDefaultLevel(ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mDefaultLevel = level; - } - - ELevel getDefaultLevel() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mDefaultLevel; - } - - void setAlwaysFlush(bool flush) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mLogAlwaysFlush = flush; - } - - bool getAlwaysFlush() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mLogAlwaysFlush; - } - - void setEnabledLogTypesMask(U32 mask) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mEnabledLogTypesMask = mask; - } - - U32 getEnabledLogTypesMask() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mEnabledLogTypesMask; - } - - void setFunctionLevel(const std::string& function_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mFunctionLevelMap[function_name] = level; - } - - void setClassLevel(const std::string& class_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mClassLevelMap[class_name] = level; - } - - void setFileLevel(const std::string& file_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mFileLevelMap[file_name] = level; - } - - void setTagLevel(const std::string& tag_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mTagLevelMap[tag_name] = level; - } - - LLError::ELevel decodeLevel(std::string name) - { - static LevelMap level_names; - if (level_names.empty()) - { - level_names["ALL"] = LLError::LEVEL_ALL; - level_names["DEBUG"] = LLError::LEVEL_DEBUG; - level_names["INFO"] = LLError::LEVEL_INFO; - level_names["WARN"] = LLError::LEVEL_WARN; - level_names["ERROR"] = LLError::LEVEL_ERROR; - level_names["NONE"] = LLError::LEVEL_NONE; - } - - std::transform(name.begin(), name.end(), name.begin(), toupper); - - LevelMap::const_iterator i = level_names.find(name); - if (i == level_names.end()) - { - LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL; - return LLError::LEVEL_INFO; - } - - return i->second; - } -} - -namespace { - void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) - { - LLSD::array_const_iterator i, end; - for (i = list.beginArray(), end = list.endArray(); i != end; ++i) - { - map[*i] = level; - } - } -} - -namespace LLError -{ - void configure(const LLSD& config) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - - s->mFunctionLevelMap.clear(); - s->mClassLevelMap.clear(); - s->mFileLevelMap.clear(); - s->mTagLevelMap.clear(); - s->mUniqueLogMessages.clear(); - - setDefaultLevel(decodeLevel(config["default-level"])); - if (config.has("log-always-flush")) - { - setAlwaysFlush(config["log-always-flush"]); - } - if (config.has("enabled-log-types-mask")) - { - setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger()); - } - - if (config.has("settings") && config["settings"].isArray()) - { - LLSD sets = config["settings"]; - LLSD::array_const_iterator a, end; - for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) - { - const LLSD& entry = *a; - if (entry.isMap() && entry.size() != 0) - { - ELevel level = decodeLevel(entry["level"]); - - setLevels(s->mFunctionLevelMap, entry["functions"], level); - setLevels(s->mClassLevelMap, entry["classes"], level); - setLevels(s->mFileLevelMap, entry["files"], level); - setLevels(s->mTagLevelMap, entry["tags"], level); - } - } - } - } -} - - -namespace LLError -{ - Recorder::Recorder() - : mWantsTime(true) - , mWantsTags(true) - , mWantsLevel(true) - , mWantsLocation(true) - , mWantsFunctionName(true) - , mWantsMultiline(false) - { - } - - Recorder::~Recorder() - { - } - - bool Recorder::wantsTime() - { - return mWantsTime; - } - - // virtual - bool Recorder::wantsTags() - { - return mWantsTags; - } - - // virtual - bool Recorder::wantsLevel() - { - return mWantsLevel; - } - - // virtual - bool Recorder::wantsLocation() - { - return mWantsLocation; - } - - // virtual - bool Recorder::wantsFunctionName() - { - return mWantsFunctionName; - } - - // virtual - bool Recorder::wantsMultiline() - { - return mWantsMultiline; - } - - void Recorder::showTime(bool show) - { - mWantsTime = show; - } - - void Recorder::showTags(bool show) - { - mWantsTags = show; - } - - void Recorder::showLevel(bool show) - { - mWantsLevel = show; - } - - void Recorder::showLocation(bool show) - { - mWantsLocation = show; - } - - void Recorder::showFunctionName(bool show) - { - mWantsFunctionName = show; - } - - void Recorder::showMultiline(bool show) - { - mWantsMultiline = show; - } - - void addRecorder(RecorderPtr recorder) - { - if (!recorder) - { - return; - } - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - s->mRecorders.push_back(recorder); - } - - void removeRecorder(RecorderPtr recorder) - { - if (!recorder) - { - return; - } - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), - s->mRecorders.end()); - } - - // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to - // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which - // points to the Recorder base class), but a shared_ptr which - // specifically points to the concrete RECORDER subclass instance, along - // with a Recorders::iterator indicating the position of that entry in - // mRecorders. The shared_ptr might be empty (operator!() returns true) if - // there was no such RECORDER subclass instance in mRecorders. - // - // NOTE!!! Requires external mutex lock!!! - template - std::pair, Recorders::iterator> - findRecorderPos(SettingsConfigPtr &s) - { - // Since we promise to return an iterator, use a classic iterator - // loop. - auto end{s->mRecorders.end()}; - for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it) - { - // *it is a RecorderPtr, a shared_ptr. Use a - // dynamic_pointer_cast to try to downcast to test if it's also a - // shared_ptr. - auto ptr = std::dynamic_pointer_cast(*it); - if (ptr) - { - // found the entry we want - return { ptr, it }; - } - } - // dropped out of the loop without finding any such entry -- instead - // of default-constructing Recorders::iterator (which might or might - // not be valid), return a value that is valid but not dereferenceable. - return { {}, end }; - } - - // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to - // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which - // points to the Recorder base class), but a shared_ptr which - // specifically points to the concrete RECORDER subclass instance. The - // shared_ptr might be empty (operator!() returns true) if there was no - // such RECORDER subclass instance in mRecorders. - template - std::shared_ptr findRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - return findRecorderPos(s).first; - } - - // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr - // points to a Recorder subclass of type RECORDER. Return true if there - // was one and we removed it, false if there wasn't one to start with. - template - bool removeRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - auto found = findRecorderPos(s); - if (found.first) - { - s->mRecorders.erase(found.second); - } - return bool(found.first); - } -} - -namespace LLError -{ - void logToFile(const std::string& file_name) - { - // remove any previous Recorder filling this role - removeRecorder(); - - if (!file_name.empty()) - { - std::shared_ptr recordToFile(new RecordToFile(file_name)); - if (recordToFile->okay()) - { - addRecorder(recordToFile); - } - } - } - - std::string logFileName() - { - auto found = findRecorder(); - return found? found->getFilename() : std::string(); - } - - void logToStderr() - { - if (! findRecorder()) - { - RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); - addRecorder(recordToStdErr); - } - } - - void logToFixedBuffer(LLLineBuffer* fixedBuffer) - { - // remove any previous Recorder filling this role - removeRecorder(); - - if (fixedBuffer) - { - RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); - addRecorder(recordToFixedBuffer); - } - } -} - -namespace -{ - std::string escapedMessageLines(const std::string& message) - { - std::ostringstream out; - size_t written_out = 0; - size_t all_content = message.length(); - size_t escape_char_index; // always relative to start of message - // Use find_first_of to find the next character in message that needs escaping - for ( escape_char_index = message.find_first_of("\\\n\r"); - escape_char_index != std::string::npos && written_out < all_content; - // record what we've written this iteration, scan for next char that needs escaping - written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out) - ) - { - // found a character that needs escaping, so write up to that with the escape prefix - // note that escape_char_index is relative to the start, not to the written_out offset - out << message.substr(written_out, escape_char_index - written_out) << '\\'; - - // write out the appropriate second character in the escape sequence - char found = message[escape_char_index]; - switch ( found ) - { - case '\\': - out << '\\'; - break; - case '\n': - out << 'n'; - break; - case '\r': - out << 'r'; - break; - } - } - - if ( written_out < all_content ) // if the loop above didn't write everything - { - // write whatever was left - out << message.substr(written_out, std::string::npos); - } - return out.str(); - } - - void writeToRecorders(const LLError::CallSite& site, const std::string& message) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLError::ELevel level = site.mLevel; - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - - std::string escaped_message; - - LLMutexLock lock(&s->mRecorderMutex); - for (LLError::RecorderPtr& r : s->mRecorders) - { - if (!r->enabled()) - { - continue; - } - - std::ostringstream message_stream; - - if (r->wantsTime() && s->mTimeFunction != NULL) - { - message_stream << s->mTimeFunction(); - } - message_stream << " "; - - if (r->wantsLevel()) - { - message_stream << site.mLevelString; - } - message_stream << " "; - - if (r->wantsTags()) - { - message_stream << site.mTagString; - } - message_stream << " "; - - if (r->wantsLocation() || level == LLError::LEVEL_ERROR) - { - message_stream << site.mLocationString; - } - message_stream << " "; - - if (r->wantsFunctionName()) - { - message_stream << site.mFunctionString; - } - message_stream << " : "; - - if (r->wantsMultiline()) - { - message_stream << message; - } - else - { - if (escaped_message.empty()) - { - escaped_message = escapedMessageLines(message); - } - message_stream << escaped_message; - } - - r->recordMessage(level, message_stream.str()); - } - } -} - -namespace { - // We need a couple different mutexes, but we want to use the same mechanism - // for both. Make getMutex() a template function with different instances - // for different MutexDiscriminator values. - enum MutexDiscriminator - { - LOG_MUTEX, - STACKS_MUTEX - }; - // Some logging calls happen very early in processing -- so early that our - // module-static variables aren't yet initialized. getMutex() wraps a - // function-static LLMutex so that early calls can still have a valid - // LLMutex instance. - template - LLMutex* getMutex() - { - // guaranteed to be initialized the first time control reaches here - static LLMutex sMutex; - return &sMutex; - } - - bool checkLevelMap(const LevelMap& map, const std::string& key, - LLError::ELevel& level) - { - bool stop_checking; - LevelMap::const_iterator i = map.find(key); - if (i == map.end()) - { - return stop_checking = false; - } - - level = i->second; - return stop_checking = true; - } - - bool checkLevelMap( const LevelMap& map, - const char *const * keys, - size_t count, - LLError::ELevel& level) - { - bool found_level = false; - - LLError::ELevel tag_level = LLError::LEVEL_NONE; - - for (size_t i = 0; i < count; i++) - { - LevelMap::const_iterator it = map.find(keys[i]); - if (it != map.end()) - { - found_level = true; - tag_level = llmin(tag_level, it->second); - } - } - - if (found_level) - { - level = tag_level; - } - return found_level; - } -} - -namespace LLError -{ - - bool Log::shouldLog(CallSite& site) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) - { - return false; - } - - Globals *g = Globals::getInstance(); - SettingsConfigPtr s = g->getSettingsConfig(); - - s->mShouldLogCallCounter++; - - const std::string& class_name = className(site.mClassInfo); - std::string function_name = functionName(site.mFunction); -#if LL_LINUX - // gross, but typeid comparison seems to always fail here with gcc4.1 - if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name())) -#else - if (site.mClassInfo != typeid(NoClassInfo)) -#endif // LL_LINUX - { - function_name = class_name + "::" + function_name; - } - - ELevel compareLevel = s->mDefaultLevel; - - // The most specific match found will be used as the log level, - // since the computation short circuits. - // So, in increasing order of importance: - // Default < Tags < File < Class < Function - checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel) - || checkLevelMap(s->mClassLevelMap, class_name, compareLevel) - || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel) - || (site.mTagCount > 0 - ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel) - : false); - - site.mCached = true; - g->addCallSite(site); - return site.mShouldLog = site.mLevel >= compareLevel; - } - - - void Log::flush(const std::ostringstream& out, const CallSite& site) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLMutexTrylock lock(getMutex(),5); - if (!lock.isLocked()) - { - return; - } - - Globals* g = Globals::getInstance(); - SettingsConfigPtr s = g->getSettingsConfig(); - - std::string message = out.str(); - - if (site.mPrintOnce) - { - std::ostringstream message_stream; - - std::map::iterator messageIter = s->mUniqueLogMessages.find(message); - if (messageIter != s->mUniqueLogMessages.end()) - { - messageIter->second++; - unsigned int num_messages = messageIter->second; - if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0) - { - message_stream << "ONCE (" << num_messages << "th time seen): "; - } - else - { - return; - } - } - else - { - message_stream << "ONCE: "; - s->mUniqueLogMessages[message] = 1; - } - message_stream << message; - message = message_stream.str(); - } - - writeToRecorders(site, message); - - if (site.mLevel == LEVEL_ERROR) - { - g->mFatalMessage = message; - if (s->mCrashFunction) - { - s->mCrashFunction(message); - } - } - } -} - -namespace LLError -{ - SettingsStoragePtr saveAndResetSettings() - { - return Globals::getInstance()->saveAndResetSettingsConfig(); - } - - void restoreSettings(SettingsStoragePtr pSettingsStorage) - { - return Globals::getInstance()->restore(pSettingsStorage); - } - - std::string removePrefix(std::string& s, const std::string& p) - { - std::string::size_type where = s.find(p); - if (where == std::string::npos) - { - return s; - } - - return std::string(s, where + p.size()); - } - - void replaceChar(std::string& s, char old, char replacement) - { - std::string::size_type i = 0; - std::string::size_type len = s.length(); - for ( ; i < len; i++ ) - { - if (s[i] == old) - { - s[i] = replacement; - } - } - } - - std::string abbreviateFile(const std::string& filePath) - { - std::string f = filePath; -#if LL_WINDOWS - replaceChar(f, '\\', '/'); -#endif - static std::string indra_prefix = "indra/"; - f = removePrefix(f, indra_prefix); - -#if LL_DARWIN - static std::string newview_prefix = "newview/../"; - f = removePrefix(f, newview_prefix); -#endif - - return f; - } - - int shouldLogCallCount() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mShouldLogCallCounter; - } - - std::string utcTime() - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - auto chars = strftime(time_str, BUF_SIZE, - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&now)); - - return chars ? time_str : "time error"; - } -} - -namespace LLError -{ - LLCallStacks::StringVector LLCallStacks::sBuffer ; - - //static - void LLCallStacks::push(const char* function, const int line) - { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) - { - return; - } - - if(sBuffer.size() > 511) - { - clear() ; - } - - std::ostringstream out; - insert(out, function, line); - sBuffer.push_back(out.str()); - } - - //static - void LLCallStacks::insert(std::ostream& out, const char* function, const int line) - { - out << function << " line " << line << " " ; - } - - //static - void LLCallStacks::end(const std::ostringstream& out) - { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) - { - return; - } - - if(sBuffer.size() > 511) - { - clear() ; - } - - sBuffer.push_back(out.str()); - } - - //static - void LLCallStacks::print() - { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) - { - return; - } - - if(! sBuffer.empty()) - { - LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL; - for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend()); - ri != re; ++ri) - { - LL_INFOS() << (*ri) << LL_ENDL; - } - LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL; - } - - cleanup(); - } - - //static - void LLCallStacks::clear() - { - sBuffer.clear(); - } - - //static - void LLCallStacks::cleanup() - { - clear(); - } - - std::ostream& operator<<(std::ostream& out, const LLStacktrace&) - { - return out << boost::stacktrace::stacktrace(); - } - - // LLOutOfMemoryWarning - std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle; - std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning; - LLUserWarningMsg::Handler LLUserWarningMsg::sHandler; - - void LLUserWarningMsg::show(const std::string& message) - { - if (sHandler) - { - sHandler(std::string(), message); - } - } - - void LLUserWarningMsg::showOutOfMemory() - { - if (sHandler && !sLocalizedOutOfMemoryTitle.empty()) - { - sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning); - } - } - - void LLUserWarningMsg::showMissingFiles() - { - // Files Are missing, likely can't localize. - const std::string error_string = - "Second Life viewer couldn't access some of the files it needs and will be closed." - "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " - "contact https://support.secondlife.com if issue persists after reinstall."; - sHandler("Missing Files", error_string); - } - - void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler) - { - sHandler = handler; - } - - void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message) - { - sLocalizedOutOfMemoryTitle = title; - sLocalizedOutOfMemoryWarning = message; - } -} - -void crashdriver(void (*callback)(int*)) -{ - // The LLERROR_CRASH macro used to have inline code of the form: - //int* make_me_crash = NULL; - //*make_me_crash = 0; - - // But compilers are getting smart enough to recognize that, so we must - // assign to an address supplied by a separate source file. We could do - // the assignment here in crashdriver() -- but then BugSplat would group - // all LL_ERRS() crashes as the fault of this one function, instead of - // identifying the specific LL_ERRS() source line. So instead, do the - // assignment in a lambda in the caller's source. We just provide the - // nullptr target. - callback(nullptr); -} +/** + * @file llerror.cpp + * @date December 2006 + * @brief error message system + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llerror.h" +#include "llerrorcontrol.h" +#include "llsdutil.h" + +#include +#ifdef __GNUC__ +# include +#endif // __GNUC__ +#include +#if !LL_WINDOWS +# include +# include +# include +#else +# include +#endif // !LL_WINDOWS +#include +#include "string.h" + +#include "llapp.h" +#include "llapr.h" +#include "llfile.h" +#include "lllivefile.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llsingleton.h" +#include "llstl.h" +#include "lltimer.h" + +// On Mac, got: +// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define +// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if +// _Unwind_Backtrace is available without `_GNU_SOURCE`." +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#include + +namespace { +#if LL_WINDOWS + void debugger_print(const std::string& s) + { + // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C + // which works just fine under the windows debugger, but can cause users who + // have enabled SEHOP exception chain validation to crash due to interactions + // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707 + // + if (IsDebuggerPresent()) + { + // Need UTF16 for Unicode OutputDebugString + // + if (s.size()) + { + OutputDebugString(utf8str_to_utf16str(s).c_str()); + OutputDebugString(TEXT("\n")); + } + } + } +#else + class RecordToSyslog : public LLError::Recorder + { + public: + RecordToSyslog(const std::string& identity) + : mIdentity(identity) + { + openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); + // we need to set the string from a local copy of the string + // since apparanetly openlog expects the const char* to remain + // valid even after it returns (presumably until closelog) + } + + ~RecordToSyslog() + { + closelog(); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x01; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + int syslogPriority = LOG_CRIT; + switch (level) { + case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; + case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; + case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; + case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; + default: syslogPriority = LOG_CRIT; + } + + syslog(syslogPriority, "%s", message.c_str()); + } + private: + std::string mIdentity; + }; +#endif + + class RecordToFile : public LLError::Recorder + { + public: + RecordToFile(const std::string& filename): + mName(filename) + { + mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app); + if (!mFile) + { + LL_INFOS() << "Error setting log file to " << filename << LL_ENDL; + } + else + { + if (!LLError::getAlwaysFlush()) + { + mFile.sync_with_stdio(false); + } + } + } + + ~RecordToFile() + { + mFile.close(); + } + + virtual bool enabled() override + { +#ifdef LL_RELEASE_FOR_DOWNLOAD + return 1; +#else + return LLError::getEnabledLogTypesMask() & 0x02; +#endif + } + + bool okay() const { return mFile.good(); } + + std::string getFilename() const { return mName; } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + if (LLError::getAlwaysFlush()) + { + mFile << message << std::endl; + } + else + { + mFile << message << "\n"; + } + } + + private: + const std::string mName; + llofstream mFile; + }; + + + class RecordToStderr : public LLError::Recorder + { + public: + RecordToStderr(bool timestamp) : mUseANSI(checkANSI()) + { + this->showMultiline(true); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x04; + } + + LL_FORCE_INLINE std::string createBoldANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "1"; + ansi_code += "m"; + + return ansi_code; + } + + LL_FORCE_INLINE std::string createResetANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "0"; + ansi_code += "m"; + + return ansi_code; + } + + LL_FORCE_INLINE std::string createANSI(const std::string& color) + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "38;5;"; + ansi_code += color; + ansi_code += "m"; + + return ansi_code; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + // The default colors for error, warn and debug are now a bit more pastel + // and easier to read on the default (black) terminal background but you + // now have the option to set the color of each via an environment variables: + // LL_ANSI_ERROR_COLOR_CODE (default is red) + // LL_ANSI_WARN_COLOR_CODE (default is blue) + // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) + // The list of color codes can be found in many places but I used this page: + // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors + // (Note: you may need to restart Visual Studio to pick environment changes) + char* val = nullptr; + std::string s_ansi_error_code = "160"; + if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); + std::string s_ansi_warn_code = "33"; + if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); + std::string s_ansi_debug_code = "177"; + if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); + + static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red + static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue + static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta + + if (mUseANSI) + { + writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error : + (level == LLError::LEVEL_WARN) ? s_ansi_warn : + s_ansi_debug, message); + } + else + { + LL_PROFILE_ZONE_NAMED("fprintf"); + fprintf(stderr, "%s\n", message.c_str()); + } + } + + private: + bool mUseANSI; + + LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + static std::string s_ansi_bold = createBoldANSI(); // bold text + static std::string s_ansi_reset = createResetANSI(); // reset + // ANSI color code escape sequence, message, and reset in one fprintf call + // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. + fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); + } + + static bool checkANSI(void) + { + // Check whether it's okay to use ANSI; if stderr is + // a tty then we assume yes. Can be turned off with + // the LL_NO_ANSI_COLOR env var. + return (0 != isatty(2)) && + (NULL == getenv("LL_NO_ANSI_COLOR")); + } + }; + + class RecordToFixedBuffer : public LLError::Recorder + { + public: + RecordToFixedBuffer(LLLineBuffer* buffer) + : mBuffer(buffer) + { + this->showMultiline(true); + this->showTags(false); + this->showLocation(false); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x08; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + mBuffer->addLine(message); + } + + private: + LLLineBuffer* mBuffer; + }; + +#if LL_WINDOWS + class RecordToWinDebug: public LLError::Recorder + { + public: + RecordToWinDebug() + { + this->showMultiline(true); + this->showTags(false); + this->showLocation(false); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x10; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + debugger_print(message); + } + }; +#endif +} + + +namespace +{ + std::string className(const std::type_info& type) + { + return LLError::Log::demangle(type.name()); + } +} // anonymous + +namespace LLError +{ + std::string Log::demangle(const char* mangled) + { +#ifdef __GNUC__ + // GCC: type_info::name() returns a mangled class name,st demangle + // passing nullptr, 0 forces allocation of a unique buffer we can free + // fixing MAINT-8724 on OSX 10.14 + int status = -1; + char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status); + std::string result(name ? name : mangled); + free(name); + return result; + +#elif LL_WINDOWS + // Visual Studio: type_info::name() includes the text "class " at the start + std::string name = mangled; + for (const auto& prefix : std::vector{ "class ", "struct " }) + { + if (0 == name.compare(0, prefix.length(), prefix)) + { + return name.substr(prefix.length()); + } + } + // huh, that's odd, we should see one or the other prefix -- but don't + // try to log unless logging is already initialized + // in Python, " or ".join(vector) -- but in C++, a PITB + LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" + << name << "'" << LL_ENDL; + return name; + +#else // neither GCC nor Visual Studio + return mangled; +#endif + } +} // LLError + +namespace +{ + std::string functionName(const std::string& preprocessor_name) + { +#if LL_WINDOWS + // DevStudio: the __FUNCTION__ macro string includes + // the type and/or namespace prefixes + + std::string::size_type p = preprocessor_name.rfind(':'); + if (p == std::string::npos) + { + return preprocessor_name; + } + return preprocessor_name.substr(p + 1); + +#else + return preprocessor_name; +#endif + } + + + class LogControlFile : public LLLiveFile + { + LOG_CLASS(LogControlFile); + + public: + static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir); + + virtual bool loadFile(); + + private: + LogControlFile(const std::string &filename) + : LLLiveFile(filename) + { } + }; + + LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir) + { + // NB: We have no abstraction in llcommon for the "proper" + // delimiter but it turns out that "/" works on all three platforms + + std::string file = user_dir + "/logcontrol-dev.xml"; + + llstat stat_info; + if (LLFile::stat(file, &stat_info)) { + // NB: stat returns non-zero if it can't read the file, for example + // if it doesn't exist. LLFile has no better abstraction for + // testing for file existence. + + file = app_dir + "/logcontrol.xml"; + } + return * new LogControlFile(file); + // NB: This instance is never freed + } + + bool LogControlFile::loadFile() + { + LLSD configuration; + + { + llifstream file(filename().c_str()); + if (!file.is_open()) + { + LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL; + return false; + } + + if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE) + { + LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL; + return false; + } + + if (! configuration || !configuration.isMap()) + { + LL_WARNS() << filename() << " missing, ill-formed, or simply undefined" + " content; not changing configuration" + << LL_ENDL; + return false; + } + } + + LLError::configure(configuration); + LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL; + return true; + } + + + typedef std::map LevelMap; + typedef std::vector Recorders; + typedef std::vector CallSiteVector; + + class SettingsConfig : public LLRefCount + { + friend class Globals; + + public: + virtual ~SettingsConfig(); + + LLError::ELevel mDefaultLevel; + + bool mLogAlwaysFlush; + + U32 mEnabledLogTypesMask; + + LevelMap mFunctionLevelMap; + LevelMap mClassLevelMap; + LevelMap mFileLevelMap; + LevelMap mTagLevelMap; + std::map mUniqueLogMessages; + + LLError::FatalFunction mCrashFunction; + LLError::TimeFunction mTimeFunction; + + Recorders mRecorders; + LLMutex mRecorderMutex; + + int mShouldLogCallCounter; + + private: + SettingsConfig(); + }; + + typedef LLPointer SettingsConfigPtr; + + SettingsConfig::SettingsConfig() + : LLRefCount(), + mDefaultLevel(LLError::LEVEL_DEBUG), + mLogAlwaysFlush(true), + mEnabledLogTypesMask(255), + mFunctionLevelMap(), + mClassLevelMap(), + mFileLevelMap(), + mTagLevelMap(), + mUniqueLogMessages(), + mCrashFunction(NULL), + mTimeFunction(NULL), + mRecorders(), + mRecorderMutex(), + mShouldLogCallCounter(0) + { + } + + SettingsConfig::~SettingsConfig() + { + mRecorders.clear(); + } + + class Globals + { + public: + static Globals* getInstance(); + protected: + Globals(); + public: + std::string mFatalMessage; + + void addCallSite(LLError::CallSite&); + void invalidateCallSites(); + + SettingsConfigPtr getSettingsConfig(); + + void resetSettingsConfig(); + LLError::SettingsStoragePtr saveAndResetSettingsConfig(); + void restore(LLError::SettingsStoragePtr pSettingsStorage); + private: + CallSiteVector callSites; + SettingsConfigPtr mSettingsConfig; + }; + + Globals::Globals() + : + callSites(), + mSettingsConfig(new SettingsConfig()) + { + } + + + Globals* Globals::getInstance() + { + // According to C++11 Function-Local Initialization + // of static variables is supposed to be thread safe + // without risk of deadlocks. + static Globals inst; + + return &inst; + } + + void Globals::addCallSite(LLError::CallSite& site) + { + callSites.push_back(&site); + } + + void Globals::invalidateCallSites() + { + for (LLError::CallSite* site : callSites) + { + site->invalidate(); + } + + callSites.clear(); + } + + SettingsConfigPtr Globals::getSettingsConfig() + { + return mSettingsConfig; + } + + void Globals::resetSettingsConfig() + { + invalidateCallSites(); + mSettingsConfig = new SettingsConfig(); + } + + LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig() + { + LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get()); + resetSettingsConfig(); + return oldSettingsConfig; + } + + void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage) + { + invalidateCallSites(); + SettingsConfigPtr newSettingsConfig(dynamic_cast(pSettingsStorage.get())); + mSettingsConfig = newSettingsConfig; + } +} + +namespace LLError +{ + CallSite::CallSite(ELevel level, + const char* file, + int line, + const std::type_info& class_info, + const char* function, + bool printOnce, + const char** tags, + size_t tag_count) + : mLevel(level), + mFile(file), + mLine(line), + mClassInfo(class_info), + mFunction(function), + mCached(false), + mShouldLog(false), + mPrintOnce(printOnce), + mTags(new const char* [tag_count]), + mTagCount(tag_count) + { + switch (mLevel) + { + case LEVEL_DEBUG: mLevelString = "DEBUG"; break; + case LEVEL_INFO: mLevelString = "INFO"; break; + case LEVEL_WARN: mLevelString = "WARNING"; break; + case LEVEL_ERROR: mLevelString = "ERROR"; break; + default: mLevelString = "XXX"; break; + }; + + mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine); +#if LL_WINDOWS + // DevStudio: __FUNCTION__ already includes the full class name +#else +#if LL_LINUX + // gross, but typeid comparison seems to always fail here with gcc4.1 + if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name())) +#else + if (mClassInfo != typeid(NoClassInfo)) +#endif // LL_LINUX + { + mFunctionString = className(mClassInfo) + "::"; + } +#endif + mFunctionString += std::string(mFunction); + + for (int i = 0; i < tag_count; i++) + { + if (strchr(tags[i], ' ')) + { + LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL; + } + mTags[i] = tags[i]; + } + + mTagString.append("#"); + // always construct a tag sequence; will be just a single # if no tag + for (size_t i = 0; i < mTagCount; i++) + { + mTagString.append(mTags[i]); + mTagString.append("#"); + } + } + + CallSite::~CallSite() + { + delete []mTags; + } + + void CallSite::invalidate() + { + mCached = false; + } +} + +namespace +{ + bool shouldLogToStderr() + { +#if LL_DARWIN + // On macOS, stderr from apps launched from the Finder goes to the + // console log. It's generally considered bad form to spam too much + // there. That scenario can be detected by noticing that stderr is a + // character device (S_IFCHR). + + // If stderr is a tty or a pipe, assume the user launched from the + // command line or debugger and therefore wants to see stderr. + if (isatty(STDERR_FILENO)) + return true; + // not a tty, but might still be a pipe -- check + struct stat st; + if (fstat(STDERR_FILENO, &st) < 0) + { + // capture errno right away, before engaging any other operations + auto errno_save = errno; + // this gets called during log-system setup -- can't log yet! + std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno " + << errno_save << std::endl; + // if we can't tell, err on the safe side and don't write stderr + return false; + } + + // fstat() worked: return true only if stderr is a pipe + return ((st.st_mode & S_IFMT) == S_IFIFO); +#else + return true; +#endif + } + + bool stderrLogWantsTime() + { +#if LL_WINDOWS + return false; +#else + return true; +#endif + } + + + void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true) + { + Globals::getInstance()->resetSettingsConfig(); + + LLError::setDefaultLevel(LLError::LEVEL_INFO); + LLError::setAlwaysFlush(true); + LLError::setEnabledLogTypesMask(0xFFFFFFFF); + LLError::setTimeFunction(LLError::utcTime); + + // log_to_stderr is only false in the unit and integration tests to keep builds quieter + if (log_to_stderr && shouldLogToStderr()) + { + LLError::logToStderr(); + } + +#if LL_WINDOWS + LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug()); + LLError::addRecorder(recordToWinDebug); +#endif + + LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir); + + // NOTE: We want to explicitly load the file before we add it to the event timer + // that checks for changes to the file. Else, we're not actually loading the file yet, + // and most of the initialization happens without any attention being paid to the + // log control file. Not to mention that when it finally gets checked later, + // all log statements that have been evaluated already become dirty and need to be + // evaluated for printing again. So, make sure to call checkAndReload() + // before addToEventTimer(). + e.checkAndReload(); + e.addToEventTimer(); + } +} + +namespace LLError +{ + void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr) + { + commonInit(user_dir, app_dir, log_to_stderr); + } + + void setFatalFunction(const FatalFunction& f) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mCrashFunction = f; + } + + FatalFunction getFatalFunction() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mCrashFunction; + } + + std::string getFatalMessage() + { + return Globals::getInstance()->mFatalMessage; + } + + void setTimeFunction(TimeFunction f) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mTimeFunction = f; + } + + void setDefaultLevel(ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mDefaultLevel = level; + } + + ELevel getDefaultLevel() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mDefaultLevel; + } + + void setAlwaysFlush(bool flush) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mLogAlwaysFlush = flush; + } + + bool getAlwaysFlush() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mLogAlwaysFlush; + } + + void setEnabledLogTypesMask(U32 mask) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mEnabledLogTypesMask = mask; + } + + U32 getEnabledLogTypesMask() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mEnabledLogTypesMask; + } + + void setFunctionLevel(const std::string& function_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mFunctionLevelMap[function_name] = level; + } + + void setClassLevel(const std::string& class_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mClassLevelMap[class_name] = level; + } + + void setFileLevel(const std::string& file_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mFileLevelMap[file_name] = level; + } + + void setTagLevel(const std::string& tag_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mTagLevelMap[tag_name] = level; + } + + LLError::ELevel decodeLevel(std::string name) + { + static LevelMap level_names; + if (level_names.empty()) + { + level_names["ALL"] = LLError::LEVEL_ALL; + level_names["DEBUG"] = LLError::LEVEL_DEBUG; + level_names["INFO"] = LLError::LEVEL_INFO; + level_names["WARN"] = LLError::LEVEL_WARN; + level_names["ERROR"] = LLError::LEVEL_ERROR; + level_names["NONE"] = LLError::LEVEL_NONE; + } + + std::transform(name.begin(), name.end(), name.begin(), toupper); + + LevelMap::const_iterator i = level_names.find(name); + if (i == level_names.end()) + { + LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL; + return LLError::LEVEL_INFO; + } + + return i->second; + } +} + +namespace { + void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) + { + LLSD::array_const_iterator i, end; + for (i = list.beginArray(), end = list.endArray(); i != end; ++i) + { + map[*i] = level; + } + } +} + +namespace LLError +{ + void configure(const LLSD& config) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + + s->mFunctionLevelMap.clear(); + s->mClassLevelMap.clear(); + s->mFileLevelMap.clear(); + s->mTagLevelMap.clear(); + s->mUniqueLogMessages.clear(); + + setDefaultLevel(decodeLevel(config["default-level"])); + if (config.has("log-always-flush")) + { + setAlwaysFlush(config["log-always-flush"]); + } + if (config.has("enabled-log-types-mask")) + { + setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger()); + } + + if (config.has("settings") && config["settings"].isArray()) + { + LLSD sets = config["settings"]; + LLSD::array_const_iterator a, end; + for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) + { + const LLSD& entry = *a; + if (entry.isMap() && entry.size() != 0) + { + ELevel level = decodeLevel(entry["level"]); + + setLevels(s->mFunctionLevelMap, entry["functions"], level); + setLevels(s->mClassLevelMap, entry["classes"], level); + setLevels(s->mFileLevelMap, entry["files"], level); + setLevels(s->mTagLevelMap, entry["tags"], level); + } + } + } + } +} + + +namespace LLError +{ + Recorder::Recorder() + : mWantsTime(true) + , mWantsTags(true) + , mWantsLevel(true) + , mWantsLocation(true) + , mWantsFunctionName(true) + , mWantsMultiline(false) + { + } + + Recorder::~Recorder() + { + } + + bool Recorder::wantsTime() + { + return mWantsTime; + } + + // virtual + bool Recorder::wantsTags() + { + return mWantsTags; + } + + // virtual + bool Recorder::wantsLevel() + { + return mWantsLevel; + } + + // virtual + bool Recorder::wantsLocation() + { + return mWantsLocation; + } + + // virtual + bool Recorder::wantsFunctionName() + { + return mWantsFunctionName; + } + + // virtual + bool Recorder::wantsMultiline() + { + return mWantsMultiline; + } + + void Recorder::showTime(bool show) + { + mWantsTime = show; + } + + void Recorder::showTags(bool show) + { + mWantsTags = show; + } + + void Recorder::showLevel(bool show) + { + mWantsLevel = show; + } + + void Recorder::showLocation(bool show) + { + mWantsLocation = show; + } + + void Recorder::showFunctionName(bool show) + { + mWantsFunctionName = show; + } + + void Recorder::showMultiline(bool show) + { + mWantsMultiline = show; + } + + void addRecorder(RecorderPtr recorder) + { + if (!recorder) + { + return; + } + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + s->mRecorders.push_back(recorder); + } + + void removeRecorder(RecorderPtr recorder) + { + if (!recorder) + { + return; + } + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), + s->mRecorders.end()); + } + + // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to + // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which + // points to the Recorder base class), but a shared_ptr which + // specifically points to the concrete RECORDER subclass instance, along + // with a Recorders::iterator indicating the position of that entry in + // mRecorders. The shared_ptr might be empty (operator!() returns true) if + // there was no such RECORDER subclass instance in mRecorders. + // + // NOTE!!! Requires external mutex lock!!! + template + std::pair, Recorders::iterator> + findRecorderPos(SettingsConfigPtr &s) + { + // Since we promise to return an iterator, use a classic iterator + // loop. + auto end{s->mRecorders.end()}; + for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it) + { + // *it is a RecorderPtr, a shared_ptr. Use a + // dynamic_pointer_cast to try to downcast to test if it's also a + // shared_ptr. + auto ptr = std::dynamic_pointer_cast(*it); + if (ptr) + { + // found the entry we want + return { ptr, it }; + } + } + // dropped out of the loop without finding any such entry -- instead + // of default-constructing Recorders::iterator (which might or might + // not be valid), return a value that is valid but not dereferenceable. + return { {}, end }; + } + + // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to + // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which + // points to the Recorder base class), but a shared_ptr which + // specifically points to the concrete RECORDER subclass instance. The + // shared_ptr might be empty (operator!() returns true) if there was no + // such RECORDER subclass instance in mRecorders. + template + std::shared_ptr findRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + return findRecorderPos(s).first; + } + + // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr + // points to a Recorder subclass of type RECORDER. Return true if there + // was one and we removed it, false if there wasn't one to start with. + template + bool removeRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + auto found = findRecorderPos(s); + if (found.first) + { + s->mRecorders.erase(found.second); + } + return bool(found.first); + } +} + +namespace LLError +{ + void logToFile(const std::string& file_name) + { + // remove any previous Recorder filling this role + removeRecorder(); + + if (!file_name.empty()) + { + std::shared_ptr recordToFile(new RecordToFile(file_name)); + if (recordToFile->okay()) + { + addRecorder(recordToFile); + } + } + } + + std::string logFileName() + { + auto found = findRecorder(); + return found? found->getFilename() : std::string(); + } + + void logToStderr() + { + if (! findRecorder()) + { + RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); + addRecorder(recordToStdErr); + } + } + + void logToFixedBuffer(LLLineBuffer* fixedBuffer) + { + // remove any previous Recorder filling this role + removeRecorder(); + + if (fixedBuffer) + { + RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); + addRecorder(recordToFixedBuffer); + } + } +} + +namespace +{ + std::string escapedMessageLines(const std::string& message) + { + std::ostringstream out; + size_t written_out = 0; + size_t all_content = message.length(); + size_t escape_char_index; // always relative to start of message + // Use find_first_of to find the next character in message that needs escaping + for ( escape_char_index = message.find_first_of("\\\n\r"); + escape_char_index != std::string::npos && written_out < all_content; + // record what we've written this iteration, scan for next char that needs escaping + written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out) + ) + { + // found a character that needs escaping, so write up to that with the escape prefix + // note that escape_char_index is relative to the start, not to the written_out offset + out << message.substr(written_out, escape_char_index - written_out) << '\\'; + + // write out the appropriate second character in the escape sequence + char found = message[escape_char_index]; + switch ( found ) + { + case '\\': + out << '\\'; + break; + case '\n': + out << 'n'; + break; + case '\r': + out << 'r'; + break; + } + } + + if ( written_out < all_content ) // if the loop above didn't write everything + { + // write whatever was left + out << message.substr(written_out, std::string::npos); + } + return out.str(); + } + + void writeToRecorders(const LLError::CallSite& site, const std::string& message) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLError::ELevel level = site.mLevel; + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + + std::string escaped_message; + + LLMutexLock lock(&s->mRecorderMutex); + for (LLError::RecorderPtr& r : s->mRecorders) + { + if (!r->enabled()) + { + continue; + } + + std::ostringstream message_stream; + + if (r->wantsTime() && s->mTimeFunction != NULL) + { + message_stream << s->mTimeFunction(); + } + message_stream << " "; + + if (r->wantsLevel()) + { + message_stream << site.mLevelString; + } + message_stream << " "; + + if (r->wantsTags()) + { + message_stream << site.mTagString; + } + message_stream << " "; + + if (r->wantsLocation() || level == LLError::LEVEL_ERROR) + { + message_stream << site.mLocationString; + } + message_stream << " "; + + if (r->wantsFunctionName()) + { + message_stream << site.mFunctionString; + } + message_stream << " : "; + + if (r->wantsMultiline()) + { + message_stream << message; + } + else + { + if (escaped_message.empty()) + { + escaped_message = escapedMessageLines(message); + } + message_stream << escaped_message; + } + + r->recordMessage(level, message_stream.str()); + } + } +} + +namespace { + // We need a couple different mutexes, but we want to use the same mechanism + // for both. Make getMutex() a template function with different instances + // for different MutexDiscriminator values. + enum MutexDiscriminator + { + LOG_MUTEX, + STACKS_MUTEX + }; + // Some logging calls happen very early in processing -- so early that our + // module-static variables aren't yet initialized. getMutex() wraps a + // function-static LLMutex so that early calls can still have a valid + // LLMutex instance. + template + LLMutex* getMutex() + { + // guaranteed to be initialized the first time control reaches here + static LLMutex sMutex; + return &sMutex; + } + + bool checkLevelMap(const LevelMap& map, const std::string& key, + LLError::ELevel& level) + { + bool stop_checking; + LevelMap::const_iterator i = map.find(key); + if (i == map.end()) + { + return stop_checking = false; + } + + level = i->second; + return stop_checking = true; + } + + bool checkLevelMap( const LevelMap& map, + const char *const * keys, + size_t count, + LLError::ELevel& level) + { + bool found_level = false; + + LLError::ELevel tag_level = LLError::LEVEL_NONE; + + for (size_t i = 0; i < count; i++) + { + LevelMap::const_iterator it = map.find(keys[i]); + if (it != map.end()) + { + found_level = true; + tag_level = llmin(tag_level, it->second); + } + } + + if (found_level) + { + level = tag_level; + } + return found_level; + } +} + +namespace LLError +{ + + bool Log::shouldLog(CallSite& site) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLMutexTrylock lock(getMutex(), 5); + if (!lock.isLocked()) + { + return false; + } + + Globals *g = Globals::getInstance(); + SettingsConfigPtr s = g->getSettingsConfig(); + + s->mShouldLogCallCounter++; + + const std::string& class_name = className(site.mClassInfo); + std::string function_name = functionName(site.mFunction); +#if LL_LINUX + // gross, but typeid comparison seems to always fail here with gcc4.1 + if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name())) +#else + if (site.mClassInfo != typeid(NoClassInfo)) +#endif // LL_LINUX + { + function_name = class_name + "::" + function_name; + } + + ELevel compareLevel = s->mDefaultLevel; + + // The most specific match found will be used as the log level, + // since the computation short circuits. + // So, in increasing order of importance: + // Default < Tags < File < Class < Function + checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel) + || checkLevelMap(s->mClassLevelMap, class_name, compareLevel) + || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel) + || (site.mTagCount > 0 + ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel) + : false); + + site.mCached = true; + g->addCallSite(site); + return site.mShouldLog = site.mLevel >= compareLevel; + } + + + void Log::flush(const std::ostringstream& out, const CallSite& site) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLMutexTrylock lock(getMutex(),5); + if (!lock.isLocked()) + { + return; + } + + Globals* g = Globals::getInstance(); + SettingsConfigPtr s = g->getSettingsConfig(); + + std::string message = out.str(); + + if (site.mPrintOnce) + { + std::ostringstream message_stream; + + std::map::iterator messageIter = s->mUniqueLogMessages.find(message); + if (messageIter != s->mUniqueLogMessages.end()) + { + messageIter->second++; + unsigned int num_messages = messageIter->second; + if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0) + { + message_stream << "ONCE (" << num_messages << "th time seen): "; + } + else + { + return; + } + } + else + { + message_stream << "ONCE: "; + s->mUniqueLogMessages[message] = 1; + } + message_stream << message; + message = message_stream.str(); + } + + writeToRecorders(site, message); + + if (site.mLevel == LEVEL_ERROR) + { + g->mFatalMessage = message; + if (s->mCrashFunction) + { + s->mCrashFunction(message); + } + } + } +} + +namespace LLError +{ + SettingsStoragePtr saveAndResetSettings() + { + return Globals::getInstance()->saveAndResetSettingsConfig(); + } + + void restoreSettings(SettingsStoragePtr pSettingsStorage) + { + return Globals::getInstance()->restore(pSettingsStorage); + } + + std::string removePrefix(std::string& s, const std::string& p) + { + std::string::size_type where = s.find(p); + if (where == std::string::npos) + { + return s; + } + + return std::string(s, where + p.size()); + } + + void replaceChar(std::string& s, char old, char replacement) + { + std::string::size_type i = 0; + std::string::size_type len = s.length(); + for ( ; i < len; i++ ) + { + if (s[i] == old) + { + s[i] = replacement; + } + } + } + + std::string abbreviateFile(const std::string& filePath) + { + std::string f = filePath; +#if LL_WINDOWS + replaceChar(f, '\\', '/'); +#endif + static std::string indra_prefix = "indra/"; + f = removePrefix(f, indra_prefix); + +#if LL_DARWIN + static std::string newview_prefix = "newview/../"; + f = removePrefix(f, newview_prefix); +#endif + + return f; + } + + int shouldLogCallCount() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mShouldLogCallCounter; + } + + std::string utcTime() + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + auto chars = strftime(time_str, BUF_SIZE, + "%Y-%m-%dT%H:%M:%SZ", + gmtime(&now)); + + return chars ? time_str : "time error"; + } +} + +namespace LLError +{ + LLCallStacks::StringVector LLCallStacks::sBuffer ; + + //static + void LLCallStacks::push(const char* function, const int line) + { + LLMutexTrylock lock(getMutex(), 5); + if (!lock.isLocked()) + { + return; + } + + if(sBuffer.size() > 511) + { + clear() ; + } + + std::ostringstream out; + insert(out, function, line); + sBuffer.push_back(out.str()); + } + + //static + void LLCallStacks::insert(std::ostream& out, const char* function, const int line) + { + out << function << " line " << line << " " ; + } + + //static + void LLCallStacks::end(const std::ostringstream& out) + { + LLMutexTrylock lock(getMutex(), 5); + if (!lock.isLocked()) + { + return; + } + + if(sBuffer.size() > 511) + { + clear() ; + } + + sBuffer.push_back(out.str()); + } + + //static + void LLCallStacks::print() + { + LLMutexTrylock lock(getMutex(), 5); + if (!lock.isLocked()) + { + return; + } + + if(! sBuffer.empty()) + { + LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL; + for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend()); + ri != re; ++ri) + { + LL_INFOS() << (*ri) << LL_ENDL; + } + LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL; + } + + cleanup(); + } + + //static + void LLCallStacks::clear() + { + sBuffer.clear(); + } + + //static + void LLCallStacks::cleanup() + { + clear(); + } + + std::ostream& operator<<(std::ostream& out, const LLStacktrace&) + { + return out << boost::stacktrace::stacktrace(); + } + + // LLOutOfMemoryWarning + std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle; + std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning; + LLUserWarningMsg::Handler LLUserWarningMsg::sHandler; + + void LLUserWarningMsg::show(const std::string& message) + { + if (sHandler) + { + sHandler(std::string(), message); + } + } + + void LLUserWarningMsg::showOutOfMemory() + { + if (sHandler && !sLocalizedOutOfMemoryTitle.empty()) + { + sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning); + } + } + + void LLUserWarningMsg::showMissingFiles() + { + // Files Are missing, likely can't localize. + const std::string error_string = + "Second Life viewer couldn't access some of the files it needs and will be closed." + "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " + "contact https://support.secondlife.com if issue persists after reinstall."; + sHandler("Missing Files", error_string); + } + + void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler) + { + sHandler = handler; + } + + void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message) + { + sLocalizedOutOfMemoryTitle = title; + sLocalizedOutOfMemoryWarning = message; + } +} + +void crashdriver(void (*callback)(int*)) +{ + // The LLERROR_CRASH macro used to have inline code of the form: + //int* make_me_crash = NULL; + //*make_me_crash = 0; + + // But compilers are getting smart enough to recognize that, so we must + // assign to an address supplied by a separate source file. We could do + // the assignment here in crashdriver() -- but then BugSplat would group + // all LL_ERRS() crashes as the fault of this one function, instead of + // identifying the specific LL_ERRS() source line. So instead, do the + // assignment in a lambda in the caller's source. We just provide the + // nullptr target. + callback(nullptr); +} diff --git a/indra/llcommon/lleventemitter.h b/indra/llcommon/lleventemitter.h index 0079104715..b9de854fda 100644 --- a/indra/llcommon/lleventemitter.h +++ b/indra/llcommon/lleventemitter.h @@ -1,102 +1,102 @@ -/** - * @file lleventemitter.h - * @brief General event emitter class - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// header guard -#ifndef LL_EVENTEMITTER_H -#define LL_EVENTEMITTER_H - -// standard headers -#include -#include -#include -#include -#include - -#include "stdtypes.h" - -/////////////////////////////////////////////////////////////////////////////// -// templatized emitter class -template < class T > -class eventEmitter -{ - public: - typedef typename T::EventType EventType; - typedef std::list< T* > ObserverContainer; - typedef void ( T::*observerMethod )( const EventType& ); - - protected: - ObserverContainer observers; - - public: - eventEmitter () { }; - - ~eventEmitter () { }; - - /////////////////////////////////////////////////////////////////////////////// - // - bool addObserver ( T* observerIn ) - { - if ( ! observerIn ) - return false; - - // check if observer already exists - if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) - return false; - - // save it - observers.push_back ( observerIn ); - - return true; - }; - - /////////////////////////////////////////////////////////////////////////////// - // - bool remObserver ( T* observerIn ) - { - if ( ! observerIn ) - return false; - - observers.remove ( observerIn ); - - return true; - }; - - /////////////////////////////////////////////////////////////////////////////// - // - void update ( observerMethod method, const EventType& msgIn ) - { - typename std::list< T* >::iterator iter = observers.begin (); - - while ( iter != observers.end () ) - { - ( ( *iter )->*method ) ( msgIn ); - - ++iter; - }; - }; -}; - -#endif // lleventemitter_h +/** + * @file lleventemitter.h + * @brief General event emitter class + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// header guard +#ifndef LL_EVENTEMITTER_H +#define LL_EVENTEMITTER_H + +// standard headers +#include +#include +#include +#include +#include + +#include "stdtypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// templatized emitter class +template < class T > +class eventEmitter +{ + public: + typedef typename T::EventType EventType; + typedef std::list< T* > ObserverContainer; + typedef void ( T::*observerMethod )( const EventType& ); + + protected: + ObserverContainer observers; + + public: + eventEmitter () { }; + + ~eventEmitter () { }; + + /////////////////////////////////////////////////////////////////////////////// + // + bool addObserver ( T* observerIn ) + { + if ( ! observerIn ) + return false; + + // check if observer already exists + if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) + return false; + + // save it + observers.push_back ( observerIn ); + + return true; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + bool remObserver ( T* observerIn ) + { + if ( ! observerIn ) + return false; + + observers.remove ( observerIn ); + + return true; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + void update ( observerMethod method, const EventType& msgIn ) + { + typename std::list< T* >::iterator iter = observers.begin (); + + while ( iter != observers.end () ) + { + ( ( *iter )->*method ) ( msgIn ); + + ++iter; + }; + }; +}; + +#endif // lleventemitter_h diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index 20c5fe7a82..e0c2381807 100644 --- a/indra/llcommon/lleventtimer.h +++ b/indra/llcommon/lleventtimer.h @@ -1,122 +1,122 @@ -/** - * @file lleventtimer.h - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_EVENTTIMER_H -#define LL_EVENTTIMER_H - -#include "stdtypes.h" -#include "lldate.h" -#include "llinstancetracker.h" -#include "lltimer.h" - -// class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LL_COMMON_API LLEventTimer : public LLInstanceTracker -{ -public: - - LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds - LLEventTimer(const LLDate& time); - virtual ~LLEventTimer(); - - //function to be called at the supplied frequency - // Normally return FALSE; TRUE will delete the timer after the function returns. - virtual bool tick() = 0; - - static void updateClass(); - - /// Schedule recurring calls to generic callable every period seconds. - /// Returns a pointer; if you delete it, cancels the recurring calls. - template - static LLEventTimer* run_every(F32 period, const CALLABLE& callable); - - /// Schedule a future call to generic callable. Returns a pointer. - /// CAUTION: The object referenced by that pointer WILL BE DELETED once - /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT - /// pointer->getInstance(pointer)!) can be used to test whether the - /// pointer is still valid. If it is, deleting it will cancel the - /// callback. - template - static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); - - /// Like run_at(), but after a time delta rather than at a timestamp. - /// Same CAUTION. - template - static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); - -protected: - LLTimer mEventTimer; - F32 mPeriod; - -private: - template - class Generic; -}; - -template -class LLEventTimer::Generic: public LLEventTimer -{ -public: - // making TIME generic allows engaging either LLEventTimer constructor - template - Generic(const TIME& time, bool once, const CALLABLE& callable): - LLEventTimer(time), - mOnce(once), - mCallable(callable) - {} - bool tick() override - { - mCallable(); - // true tells updateClass() to delete this instance - return mOnce; - } - -private: - bool mOnce; - CALLABLE mCallable; -}; - -template -LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) -{ - // return false to schedule recurring calls - return new Generic(period, false, callable); -} - -template -LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) -{ - // return true for one-shot callback - return new Generic(time, true, callable); -} - -template -LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) -{ - // one-shot callback after specified interval - return new Generic(interval, true, callable); -} - -#endif //LL_EVENTTIMER_H +/** + * @file lleventtimer.h + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_EVENTTIMER_H +#define LL_EVENTTIMER_H + +#include "stdtypes.h" +#include "lldate.h" +#include "llinstancetracker.h" +#include "lltimer.h" + +// class for scheduling a function to be called at a given frequency (approximate, inprecise) +class LL_COMMON_API LLEventTimer : public LLInstanceTracker +{ +public: + + LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds + LLEventTimer(const LLDate& time); + virtual ~LLEventTimer(); + + //function to be called at the supplied frequency + // Normally return FALSE; TRUE will delete the timer after the function returns. + virtual bool tick() = 0; + + static void updateClass(); + + /// Schedule recurring calls to generic callable every period seconds. + /// Returns a pointer; if you delete it, cancels the recurring calls. + template + static LLEventTimer* run_every(F32 period, const CALLABLE& callable); + + /// Schedule a future call to generic callable. Returns a pointer. + /// CAUTION: The object referenced by that pointer WILL BE DELETED once + /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT + /// pointer->getInstance(pointer)!) can be used to test whether the + /// pointer is still valid. If it is, deleting it will cancel the + /// callback. + template + static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); + + /// Like run_at(), but after a time delta rather than at a timestamp. + /// Same CAUTION. + template + static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); + +protected: + LLTimer mEventTimer; + F32 mPeriod; + +private: + template + class Generic; +}; + +template +class LLEventTimer::Generic: public LLEventTimer +{ +public: + // making TIME generic allows engaging either LLEventTimer constructor + template + Generic(const TIME& time, bool once, const CALLABLE& callable): + LLEventTimer(time), + mOnce(once), + mCallable(callable) + {} + bool tick() override + { + mCallable(); + // true tells updateClass() to delete this instance + return mOnce; + } + +private: + bool mOnce; + CALLABLE mCallable; +}; + +template +LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) +{ + // return false to schedule recurring calls + return new Generic(period, false, callable); +} + +template +LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) +{ + // return true for one-shot callback + return new Generic(time, true, callable); +} + +template +LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) +{ + // one-shot callback after specified interval + return new Generic(interval, true, callable); +} + +#endif //LL_EVENTTIMER_H diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index 8694129c77..a0080b57bb 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -1,150 +1,150 @@ -/** - * @file llframetimer.cpp - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "u64.h" - -#include "llframetimer.h" - -// Static members -//LLTimer LLFrameTimer::sInternalTimer; -U64 LLFrameTimer::sStartTotalTime = totalTime(); -F64 LLFrameTimer::sFrameTime = 0.0; -U64 LLFrameTimer::sTotalTime = 0; -F64 LLFrameTimer::sTotalSeconds = 0.0; -S32 LLFrameTimer::sFrameCount = 0; -U64 LLFrameTimer::sFrameDeltaTime = 0; -const F64 USEC_TO_SEC_F64 = 0.000001; - -// static -void LLFrameTimer::updateFrameTime() -{ - U64 total_time = totalTime(); - sFrameDeltaTime = total_time - sTotalTime; - sTotalTime = total_time; - sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64; - sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; -} - -void LLFrameTimer::start() -{ - reset(); - mStarted = true; -} - -void LLFrameTimer::stop() -{ - mStarted = false; -} - -void LLFrameTimer::reset() -{ - mStartTime = sFrameTime; - mExpiry = sFrameTime; -} - -void LLFrameTimer::resetWithExpiry(F32 expiration) -{ - reset(); - setTimerExpirySec(expiration); -} - -// Don't combine pause/unpause with start/stop -// Useage: -// LLFrameTime foo; // starts automatically -// foo.unpause(); // noop but safe -// foo.pause(); // pauses timer -// foo.unpause() // unpauses -// F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause() -// Note: elapsed would also be valid with no unpause() call (= time run until pause() called) -void LLFrameTimer::pause() -{ - if (mStarted) - mStartTime = sFrameTime - mStartTime; // save dtime - mStarted = false; -} - -void LLFrameTimer::unpause() -{ - if (!mStarted) - mStartTime = sFrameTime - mStartTime; // restore dtime - mStarted = true; -} - -void LLFrameTimer::setTimerExpirySec(F32 expiration) -{ - mExpiry = expiration + mStartTime; -} - -void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) -{ - mStartTime = sFrameTime; - mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); -} - -F64 LLFrameTimer::expiresAt() const -{ - F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64; - expires_at += mExpiry; - return expires_at; -} - -bool LLFrameTimer::checkExpirationAndReset(F32 expiration) -{ - //LL_INFOS() << "LLFrameTimer::checkExpirationAndReset()" << LL_ENDL; - //LL_INFOS() << " mStartTime:" << mStartTime << LL_ENDL; - //LL_INFOS() << " sFrameTime:" << sFrameTime << LL_ENDL; - //LL_INFOS() << " mExpiry: " << mExpiry << LL_ENDL; - - if(hasExpired()) - { - reset(); - setTimerExpirySec(expiration); - return true; - } - return false; -} - -// static -F32 LLFrameTimer::getFrameDeltaTimeF32() -{ - return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64); -} - - -// static -// Return seconds since the current frame started -F32 LLFrameTimer::getCurrentFrameTime() -{ - U64 frame_time = totalTime() - sTotalTime; - return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64); -} - -// Glue code to avoid full class .h file #includes -F32 getCurrentFrameTime() -{ - return (F32)(LLFrameTimer::getCurrentFrameTime()); -} +/** + * @file llframetimer.cpp + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "u64.h" + +#include "llframetimer.h" + +// Static members +//LLTimer LLFrameTimer::sInternalTimer; +U64 LLFrameTimer::sStartTotalTime = totalTime(); +F64 LLFrameTimer::sFrameTime = 0.0; +U64 LLFrameTimer::sTotalTime = 0; +F64 LLFrameTimer::sTotalSeconds = 0.0; +S32 LLFrameTimer::sFrameCount = 0; +U64 LLFrameTimer::sFrameDeltaTime = 0; +const F64 USEC_TO_SEC_F64 = 0.000001; + +// static +void LLFrameTimer::updateFrameTime() +{ + U64 total_time = totalTime(); + sFrameDeltaTime = total_time - sTotalTime; + sTotalTime = total_time; + sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64; + sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; +} + +void LLFrameTimer::start() +{ + reset(); + mStarted = true; +} + +void LLFrameTimer::stop() +{ + mStarted = false; +} + +void LLFrameTimer::reset() +{ + mStartTime = sFrameTime; + mExpiry = sFrameTime; +} + +void LLFrameTimer::resetWithExpiry(F32 expiration) +{ + reset(); + setTimerExpirySec(expiration); +} + +// Don't combine pause/unpause with start/stop +// Useage: +// LLFrameTime foo; // starts automatically +// foo.unpause(); // noop but safe +// foo.pause(); // pauses timer +// foo.unpause() // unpauses +// F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause() +// Note: elapsed would also be valid with no unpause() call (= time run until pause() called) +void LLFrameTimer::pause() +{ + if (mStarted) + mStartTime = sFrameTime - mStartTime; // save dtime + mStarted = false; +} + +void LLFrameTimer::unpause() +{ + if (!mStarted) + mStartTime = sFrameTime - mStartTime; // restore dtime + mStarted = true; +} + +void LLFrameTimer::setTimerExpirySec(F32 expiration) +{ + mExpiry = expiration + mStartTime; +} + +void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) +{ + mStartTime = sFrameTime; + mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); +} + +F64 LLFrameTimer::expiresAt() const +{ + F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64; + expires_at += mExpiry; + return expires_at; +} + +bool LLFrameTimer::checkExpirationAndReset(F32 expiration) +{ + //LL_INFOS() << "LLFrameTimer::checkExpirationAndReset()" << LL_ENDL; + //LL_INFOS() << " mStartTime:" << mStartTime << LL_ENDL; + //LL_INFOS() << " sFrameTime:" << sFrameTime << LL_ENDL; + //LL_INFOS() << " mExpiry: " << mExpiry << LL_ENDL; + + if(hasExpired()) + { + reset(); + setTimerExpirySec(expiration); + return true; + } + return false; +} + +// static +F32 LLFrameTimer::getFrameDeltaTimeF32() +{ + return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64); +} + + +// static +// Return seconds since the current frame started +F32 LLFrameTimer::getCurrentFrameTime() +{ + U64 frame_time = totalTime() - sTotalTime; + return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64); +} + +// Glue code to avoid full class .h file #includes +F32 getCurrentFrameTime() +{ + return (F32)(LLFrameTimer::getCurrentFrameTime()); +} diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index b519a637e3..ba4f075b57 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -1,151 +1,151 @@ -/** - * @file llframetimer.h - * @brief A lightweight timer that measures seconds and is only - * updated once per frame. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFRAMETIMER_H -#define LL_LLFRAMETIMER_H - -/** - * *NOTE: Because of limitations on linux which we do not really have - * time to explore, the total time is derived from the frame time - * and is recsynchronized on every frame. - */ - -#include "lltimer.h" - -class LL_COMMON_API LLFrameTimer -{ -public: - LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(true) {} - - // Return the number of seconds since the start of this - // application instance. - static F64SecondsImplicit getElapsedSeconds() - { - // Loses msec precision after ~4.5 hours... - return sFrameTime; - } - - // Return a low precision usec since epoch - static U64 getTotalTime() - { - return sTotalTime ? U64MicrosecondsImplicit(sTotalTime) : totalTime(); - } - - // Return a low precision seconds since epoch - static F64 getTotalSeconds() - { - return sTotalSeconds; - } - - // Call this method once per frame to update the current frame time. This is actually called - // at some other times as well - static void updateFrameTime(); - - // Call this method once, and only once, per frame to update the current frame count. - static void updateFrameCount() { sFrameCount++; } - - static U32 getFrameCount() { return sFrameCount; } - - static F32 getFrameDeltaTimeF32(); - - // Return seconds since the current frame started - static F32 getCurrentFrameTime(); - - // MANIPULATORS - void start(); - void stop(); - void reset(); - void resetWithExpiry(F32 expiration); - void pause(); - void unpause(); - void setTimerExpirySec(F32 expiration); - void setExpiryAt(F64 seconds_since_epoch); - bool checkExpirationAndReset(F32 expiration); - F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; } - - void setAge(const F64 age) { mStartTime = sFrameTime - age; } - - // ACCESSORS - bool hasExpired() const { return (sFrameTime >= mExpiry); } - F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); } - F32 getElapsedTimeF32() const { return mStarted ? (F32)(sFrameTime - mStartTime) : (F32)mStartTime; } - bool getStarted() const { return mStarted; } - - // return the seconds since epoch when this timer will expire. - F64 expiresAt() const; - -protected: - // A single, high resolution timer that drives all LLFrameTimers - // *NOTE: no longer used. - //static LLTimer sInternalTimer; - - // - // Aplication constants - // - - // Start time of opp in usec since epoch - static U64 sStartTotalTime; - - // - // Data updated per frame - // - - // Seconds since application start - static F64 sFrameTime; - - // Time that has elapsed since last call to updateFrameTime() - static U64 sFrameDeltaTime; - - // Total microseconds since epoch. - static U64 sTotalTime; - - // Seconds since epoch. - static F64 sTotalSeconds; - - // Total number of frames elapsed in application - static S32 sFrameCount; - - // - // Member data - // - - // Number of seconds after application start when this timer was - // started. Set equal to sFrameTime when reset. - F64 mStartTime; - - // Timer expires this many seconds after application start time. - F64 mExpiry; - - // Useful bit of state usually associated with timers, but does - // not affect actual functionality - bool mStarted; -}; - -// Glue code for Havok (or anything else that doesn't want the full .h files) -extern F32 getCurrentFrameTime(); - -#endif // LL_LLFRAMETIMER_H +/** + * @file llframetimer.h + * @brief A lightweight timer that measures seconds and is only + * updated once per frame. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFRAMETIMER_H +#define LL_LLFRAMETIMER_H + +/** + * *NOTE: Because of limitations on linux which we do not really have + * time to explore, the total time is derived from the frame time + * and is recsynchronized on every frame. + */ + +#include "lltimer.h" + +class LL_COMMON_API LLFrameTimer +{ +public: + LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(true) {} + + // Return the number of seconds since the start of this + // application instance. + static F64SecondsImplicit getElapsedSeconds() + { + // Loses msec precision after ~4.5 hours... + return sFrameTime; + } + + // Return a low precision usec since epoch + static U64 getTotalTime() + { + return sTotalTime ? U64MicrosecondsImplicit(sTotalTime) : totalTime(); + } + + // Return a low precision seconds since epoch + static F64 getTotalSeconds() + { + return sTotalSeconds; + } + + // Call this method once per frame to update the current frame time. This is actually called + // at some other times as well + static void updateFrameTime(); + + // Call this method once, and only once, per frame to update the current frame count. + static void updateFrameCount() { sFrameCount++; } + + static U32 getFrameCount() { return sFrameCount; } + + static F32 getFrameDeltaTimeF32(); + + // Return seconds since the current frame started + static F32 getCurrentFrameTime(); + + // MANIPULATORS + void start(); + void stop(); + void reset(); + void resetWithExpiry(F32 expiration); + void pause(); + void unpause(); + void setTimerExpirySec(F32 expiration); + void setExpiryAt(F64 seconds_since_epoch); + bool checkExpirationAndReset(F32 expiration); + F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; } + + void setAge(const F64 age) { mStartTime = sFrameTime - age; } + + // ACCESSORS + bool hasExpired() const { return (sFrameTime >= mExpiry); } + F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); } + F32 getElapsedTimeF32() const { return mStarted ? (F32)(sFrameTime - mStartTime) : (F32)mStartTime; } + bool getStarted() const { return mStarted; } + + // return the seconds since epoch when this timer will expire. + F64 expiresAt() const; + +protected: + // A single, high resolution timer that drives all LLFrameTimers + // *NOTE: no longer used. + //static LLTimer sInternalTimer; + + // + // Aplication constants + // + + // Start time of opp in usec since epoch + static U64 sStartTotalTime; + + // + // Data updated per frame + // + + // Seconds since application start + static F64 sFrameTime; + + // Time that has elapsed since last call to updateFrameTime() + static U64 sFrameDeltaTime; + + // Total microseconds since epoch. + static U64 sTotalTime; + + // Seconds since epoch. + static F64 sTotalSeconds; + + // Total number of frames elapsed in application + static S32 sFrameCount; + + // + // Member data + // + + // Number of seconds after application start when this timer was + // started. Set equal to sFrameTime when reset. + F64 mStartTime; + + // Timer expires this many seconds after application start time. + F64 mExpiry; + + // Useful bit of state usually associated with timers, but does + // not affect actual functionality + bool mStarted; +}; + +// Glue code for Havok (or anything else that doesn't want the full .h files) +extern F32 getCurrentFrameTime(); + +#endif // LL_LLFRAMETIMER_H diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index 0909acb747..8ee0e08c69 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -1,331 +1,331 @@ -/** - * @file llkeythrottle.h - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLKEY_THROTTLE_H -#define LL_LLKEY_THROTTLE_H - -// LLKeyThrottle keeps track of the number of action occurences with a key value -// for a type over a given time period. If the rate set in the constructor is -// exceeed, the key is considered blocked. The transition from unblocked to -// blocked is noted so the responsible agent can be informed. This transition -// takes twice the look back window to clear. - -#include "linden_common.h" - -#include "llframetimer.h" -#include - - -// forward declaration so LLKeyThrottleImpl can befriend it -template class LLKeyThrottle; - - -// Implementation utility class - use LLKeyThrottle, not this -template -class LLKeyThrottleImpl -{ - friend class LLKeyThrottle; -protected: - struct Entry { - U32 count; - bool blocked; - - Entry() : count(0), blocked(false) { } - }; - - typedef std::map EntryMap; - - EntryMap* prevMap; - EntryMap* currMap; - - U32 countLimit; - // maximum number of keys allowed per interval - - U64 intervalLength; // each map covers this time period (usec or frame number) - U64 startTime; // start of the time period (usec or frame number) - // currMap started counting at this time - // prevMap covers the previous interval - - LLKeyThrottleImpl() : - prevMap(NULL), - currMap(NULL), - countLimit(0), - intervalLength(1), - startTime(0) - {} - - static U64 getTime() - { - return LLFrameTimer::getTotalTime(); - } - static U64 getFrame() // Return the current frame number - { - return (U64) LLFrameTimer::getFrameCount(); - } -}; - - -template< class T > -class LLKeyThrottle -{ -public: - // @param realtime = false for frame-based throttle, true for usec - // real-time throttle - LLKeyThrottle(U32 limit, F32 interval, bool realtime = true) - : m(* new LLKeyThrottleImpl) - { - setParameters( limit, interval, realtime ); - } - - ~LLKeyThrottle() - { - delete m.prevMap; - delete m.currMap; - delete &m; - } - - enum State { - THROTTLE_OK, // rate not exceeded, let pass - THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time - THROTTLE_BLOCKED, // rate exceed, block key - }; - - F64 getActionCount(const T& id) - { - U64 now = 0; - if ( mIsRealtime ) - { - now = LLKeyThrottleImpl::getTime(); - } - else - { - now = LLKeyThrottleImpl::getFrame(); - } - - if (now >= (m.startTime + m.intervalLength)) - { - if (now < (m.startTime + 2 * m.intervalLength)) - { - // prune old data - delete m.prevMap; - m.prevMap = m.currMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime += m.intervalLength; - } - else - { - // lots of time has passed, all data is stale - delete m.prevMap; - delete m.currMap; - m.prevMap = new typename LLKeyThrottleImpl::EntryMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - - typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - } - - typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; - - // curr.count is the number of keys in - // this current 'time slice' from the beginning of it until now - // prevCount is the number of keys in the previous - // time slice scaled to be one full time slice back from the current - // (now) time. - - // compute current, windowed rate - F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); - F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); - return averageCount; - } - - // call each time the key wants use - State noteAction(const T& id, S32 weight = 1) - { - U64 now = 0; - if ( mIsRealtime ) - { - now = LLKeyThrottleImpl::getTime(); - } - else - { - now = LLKeyThrottleImpl::getFrame(); - } - - if (now >= (m.startTime + m.intervalLength)) - { - if (now < (m.startTime + 2 * m.intervalLength)) - { - // prune old data - delete m.prevMap; - m.prevMap = m.currMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime += m.intervalLength; - } - else - { - // lots of time has passed, all data is stale - delete m.prevMap; - delete m.currMap; - m.prevMap = new typename LLKeyThrottleImpl::EntryMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - bool prevBlocked = false; - - typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - prevBlocked = prev->second.blocked; - } - - typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; - - bool wereBlocked = curr.blocked || prevBlocked; - - curr.count += weight; - - // curr.count is the number of keys in - // this current 'time slice' from the beginning of it until now - // prevCount is the number of keys in the previous - // time slice scaled to be one full time slice back from the current - // (now) time. - - // compute current, windowed rate - F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); - F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); - - curr.blocked |= averageCount > m.countLimit; - - bool nowBlocked = curr.blocked || prevBlocked; - - if (!nowBlocked) - { - return THROTTLE_OK; - } - else if (!wereBlocked) - { - return THROTTLE_NEWLY_BLOCKED; - } - else - { - return THROTTLE_BLOCKED; - } - } - - // call to force throttle conditions for id - void throttleAction(const T& id) - { - noteAction(id); - typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; - curr.count = llmax(m.countLimit, curr.count); - curr.blocked = true; - } - - // returns true if key is blocked - bool isThrottled(const T& id) const - { - if (m.currMap->empty() - && m.prevMap->empty()) - { - // most of the time we'll fall in here - return false; - } - - // NOTE, we ignore the case where id is in the map but the map is stale. - // You might think that we'd stop throttling things in such a case, - // however it may be that a god has disabled scripts in the region or - // estate --> we probably want to report the state of the id when the - // scripting engine was paused. - typename LLKeyThrottleImpl::EntryMap::const_iterator entry = m.currMap->find(id); - if (entry != m.currMap->end()) - { - return entry->second.blocked; - } - entry = m.prevMap->find(id); - if (entry != m.prevMap->end()) - { - return entry->second.blocked; - } - return false; - } - - // Get the throttling parameters - void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime ) - { - out_limit = m.countLimit; - out_interval = m.intervalLength; - out_realtime = mIsRealtime; - } - - // Set the throttling behavior - void setParameters( U32 limit, F32 interval, bool realtime = true ) - { - // limit is the maximum number of keys - // allowed per interval (in seconds or frames) - mIsRealtime = realtime; - m.countLimit = limit; - if ( mIsRealtime ) - { - m.intervalLength = (U64)(interval * USEC_PER_SEC); - m.startTime = LLKeyThrottleImpl::getTime(); - } - else - { - m.intervalLength = (U64)interval; - m.startTime = LLKeyThrottleImpl::getFrame(); - } - - if ( m.intervalLength == 0 ) - { // Don't allow zero intervals - m.intervalLength = 1; - } - - delete m.prevMap; - m.prevMap = new typename LLKeyThrottleImpl::EntryMap; - delete m.currMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - } - -protected: - LLKeyThrottleImpl& m; - bool mIsRealtime; // true to be time based (default), false for frame based -}; - -#endif +/** + * @file llkeythrottle.h + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLKEY_THROTTLE_H +#define LL_LLKEY_THROTTLE_H + +// LLKeyThrottle keeps track of the number of action occurences with a key value +// for a type over a given time period. If the rate set in the constructor is +// exceeed, the key is considered blocked. The transition from unblocked to +// blocked is noted so the responsible agent can be informed. This transition +// takes twice the look back window to clear. + +#include "linden_common.h" + +#include "llframetimer.h" +#include + + +// forward declaration so LLKeyThrottleImpl can befriend it +template class LLKeyThrottle; + + +// Implementation utility class - use LLKeyThrottle, not this +template +class LLKeyThrottleImpl +{ + friend class LLKeyThrottle; +protected: + struct Entry { + U32 count; + bool blocked; + + Entry() : count(0), blocked(false) { } + }; + + typedef std::map EntryMap; + + EntryMap* prevMap; + EntryMap* currMap; + + U32 countLimit; + // maximum number of keys allowed per interval + + U64 intervalLength; // each map covers this time period (usec or frame number) + U64 startTime; // start of the time period (usec or frame number) + // currMap started counting at this time + // prevMap covers the previous interval + + LLKeyThrottleImpl() : + prevMap(NULL), + currMap(NULL), + countLimit(0), + intervalLength(1), + startTime(0) + {} + + static U64 getTime() + { + return LLFrameTimer::getTotalTime(); + } + static U64 getFrame() // Return the current frame number + { + return (U64) LLFrameTimer::getFrameCount(); + } +}; + + +template< class T > +class LLKeyThrottle +{ +public: + // @param realtime = false for frame-based throttle, true for usec + // real-time throttle + LLKeyThrottle(U32 limit, F32 interval, bool realtime = true) + : m(* new LLKeyThrottleImpl) + { + setParameters( limit, interval, realtime ); + } + + ~LLKeyThrottle() + { + delete m.prevMap; + delete m.currMap; + delete &m; + } + + enum State { + THROTTLE_OK, // rate not exceeded, let pass + THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time + THROTTLE_BLOCKED, // rate exceed, block key + }; + + F64 getActionCount(const T& id) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl::getTime(); + } + else + { + now = LLKeyThrottleImpl::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl::EntryMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + + typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + } + + typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + return averageCount; + } + + // call each time the key wants use + State noteAction(const T& id, S32 weight = 1) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl::getTime(); + } + else + { + now = LLKeyThrottleImpl::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl::EntryMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + bool prevBlocked = false; + + typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + prevBlocked = prev->second.blocked; + } + + typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; + + bool wereBlocked = curr.blocked || prevBlocked; + + curr.count += weight; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + + curr.blocked |= averageCount > m.countLimit; + + bool nowBlocked = curr.blocked || prevBlocked; + + if (!nowBlocked) + { + return THROTTLE_OK; + } + else if (!wereBlocked) + { + return THROTTLE_NEWLY_BLOCKED; + } + else + { + return THROTTLE_BLOCKED; + } + } + + // call to force throttle conditions for id + void throttleAction(const T& id) + { + noteAction(id); + typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; + curr.count = llmax(m.countLimit, curr.count); + curr.blocked = true; + } + + // returns true if key is blocked + bool isThrottled(const T& id) const + { + if (m.currMap->empty() + && m.prevMap->empty()) + { + // most of the time we'll fall in here + return false; + } + + // NOTE, we ignore the case where id is in the map but the map is stale. + // You might think that we'd stop throttling things in such a case, + // however it may be that a god has disabled scripts in the region or + // estate --> we probably want to report the state of the id when the + // scripting engine was paused. + typename LLKeyThrottleImpl::EntryMap::const_iterator entry = m.currMap->find(id); + if (entry != m.currMap->end()) + { + return entry->second.blocked; + } + entry = m.prevMap->find(id); + if (entry != m.prevMap->end()) + { + return entry->second.blocked; + } + return false; + } + + // Get the throttling parameters + void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime ) + { + out_limit = m.countLimit; + out_interval = m.intervalLength; + out_realtime = mIsRealtime; + } + + // Set the throttling behavior + void setParameters( U32 limit, F32 interval, bool realtime = true ) + { + // limit is the maximum number of keys + // allowed per interval (in seconds or frames) + mIsRealtime = realtime; + m.countLimit = limit; + if ( mIsRealtime ) + { + m.intervalLength = (U64)(interval * USEC_PER_SEC); + m.startTime = LLKeyThrottleImpl::getTime(); + } + else + { + m.intervalLength = (U64)interval; + m.startTime = LLKeyThrottleImpl::getFrame(); + } + + if ( m.intervalLength == 0 ) + { // Don't allow zero intervals + m.intervalLength = 1; + } + + delete m.prevMap; + m.prevMap = new typename LLKeyThrottleImpl::EntryMap; + delete m.currMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + } + +protected: + LLKeyThrottleImpl& m; + bool mIsRealtime; // true to be time based (default), false for frame based +}; + +#endif diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index 3fe5f4bdb6..58de61a7e4 100644 --- a/indra/llcommon/lllivefile.cpp +++ b/indra/llcommon/lllivefile.cpp @@ -1,198 +1,198 @@ -/** - * @file lllivefile.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "lllivefile.h" -#include "llframetimer.h" -#include "lleventtimer.h" - -const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f; - - -class LLLiveFile::Impl -{ -public: - Impl(const std::string& filename, const F32 refresh_period); - ~Impl(); - - bool check(); - void changed(); - - bool mForceCheck; - F32 mRefreshPeriod; - LLFrameTimer mRefreshTimer; - - std::string mFilename; - time_t mLastModTime; - time_t mLastStatTime; - bool mLastExists; - - LLEventTimer* mEventTimer; -private: - LOG_CLASS(LLLiveFile); -}; - -LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period) - : - mForceCheck(true), - mRefreshPeriod(refresh_period), - mFilename(filename), - mLastModTime(0), - mLastStatTime(0), - mLastExists(false), - mEventTimer(NULL) -{ -} - -LLLiveFile::Impl::~Impl() -{ - delete mEventTimer; -} - -LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period) - : impl(* new Impl(filename, refresh_period)) -{ -} - -LLLiveFile::~LLLiveFile() -{ - delete &impl; -} - - -bool LLLiveFile::Impl::check() -{ - bool detected_change = false; - // Skip the check if not enough time has elapsed and we're not - // forcing a check of the file - if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod) - { - mForceCheck = false; // force only forces one check - mRefreshTimer.reset(); // don't check again until mRefreshPeriod has passed - - // Stat the file to see if it exists and when it was last modified. - llstat stat_data; - if (LLFile::stat(mFilename, &stat_data)) - { - // Couldn't stat the file, that means it doesn't exist or is - // broken somehow. - if (mLastExists) - { - mLastExists = false; - detected_change = true; // no longer existing is a change! - LL_DEBUGS() << "detected deleted file '" << mFilename << "'" << LL_ENDL; - } - } - else - { - // The file exists - if ( ! mLastExists ) - { - // last check, it did not exist - that counts as a change - LL_DEBUGS() << "detected created file '" << mFilename << "'" << LL_ENDL; - detected_change = true; - } - else if ( stat_data.st_mtime > mLastModTime ) - { - // file modification time is newer than last check - LL_DEBUGS() << "detected updated file '" << mFilename << "'" << LL_ENDL; - detected_change = true; - } - mLastExists = true; - mLastStatTime = stat_data.st_mtime; - } - } - if (detected_change) - { - LL_INFOS() << "detected file change '" << mFilename << "'" << LL_ENDL; - } - return detected_change; -} - -void LLLiveFile::Impl::changed() -{ - // we wanted to read this file, and we were successful. - mLastModTime = mLastStatTime; -} - -bool LLLiveFile::checkAndReload() -{ - bool changed = impl.check(); - if (changed) - { - if(loadFile()) - { - impl.changed(); - this->changed(); - } - else - { - changed = false; - } - } - return changed; -} - -std::string LLLiveFile::filename() const -{ - return impl.mFilename; -} - -namespace -{ - class LiveFileEventTimer : public LLEventTimer - { - public: - LiveFileEventTimer(LLLiveFile& f, F32 refresh) - : LLEventTimer(refresh), mLiveFile(f) - { } - - bool tick() - { - mLiveFile.checkAndReload(); - return false; - } - - private: - LLLiveFile& mLiveFile; - }; - -} - -void LLLiveFile::addToEventTimer() -{ - impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod); -} - -void LLLiveFile::setRefreshPeriod(F32 seconds) -{ - if (seconds < 0.f) - { - seconds = -seconds; - } - impl.mRefreshPeriod = seconds; -} - +/** + * @file lllivefile.cpp + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lllivefile.h" +#include "llframetimer.h" +#include "lleventtimer.h" + +const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f; + + +class LLLiveFile::Impl +{ +public: + Impl(const std::string& filename, const F32 refresh_period); + ~Impl(); + + bool check(); + void changed(); + + bool mForceCheck; + F32 mRefreshPeriod; + LLFrameTimer mRefreshTimer; + + std::string mFilename; + time_t mLastModTime; + time_t mLastStatTime; + bool mLastExists; + + LLEventTimer* mEventTimer; +private: + LOG_CLASS(LLLiveFile); +}; + +LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period) + : + mForceCheck(true), + mRefreshPeriod(refresh_period), + mFilename(filename), + mLastModTime(0), + mLastStatTime(0), + mLastExists(false), + mEventTimer(NULL) +{ +} + +LLLiveFile::Impl::~Impl() +{ + delete mEventTimer; +} + +LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period) + : impl(* new Impl(filename, refresh_period)) +{ +} + +LLLiveFile::~LLLiveFile() +{ + delete &impl; +} + + +bool LLLiveFile::Impl::check() +{ + bool detected_change = false; + // Skip the check if not enough time has elapsed and we're not + // forcing a check of the file + if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod) + { + mForceCheck = false; // force only forces one check + mRefreshTimer.reset(); // don't check again until mRefreshPeriod has passed + + // Stat the file to see if it exists and when it was last modified. + llstat stat_data; + if (LLFile::stat(mFilename, &stat_data)) + { + // Couldn't stat the file, that means it doesn't exist or is + // broken somehow. + if (mLastExists) + { + mLastExists = false; + detected_change = true; // no longer existing is a change! + LL_DEBUGS() << "detected deleted file '" << mFilename << "'" << LL_ENDL; + } + } + else + { + // The file exists + if ( ! mLastExists ) + { + // last check, it did not exist - that counts as a change + LL_DEBUGS() << "detected created file '" << mFilename << "'" << LL_ENDL; + detected_change = true; + } + else if ( stat_data.st_mtime > mLastModTime ) + { + // file modification time is newer than last check + LL_DEBUGS() << "detected updated file '" << mFilename << "'" << LL_ENDL; + detected_change = true; + } + mLastExists = true; + mLastStatTime = stat_data.st_mtime; + } + } + if (detected_change) + { + LL_INFOS() << "detected file change '" << mFilename << "'" << LL_ENDL; + } + return detected_change; +} + +void LLLiveFile::Impl::changed() +{ + // we wanted to read this file, and we were successful. + mLastModTime = mLastStatTime; +} + +bool LLLiveFile::checkAndReload() +{ + bool changed = impl.check(); + if (changed) + { + if(loadFile()) + { + impl.changed(); + this->changed(); + } + else + { + changed = false; + } + } + return changed; +} + +std::string LLLiveFile::filename() const +{ + return impl.mFilename; +} + +namespace +{ + class LiveFileEventTimer : public LLEventTimer + { + public: + LiveFileEventTimer(LLLiveFile& f, F32 refresh) + : LLEventTimer(refresh), mLiveFile(f) + { } + + bool tick() + { + mLiveFile.checkAndReload(); + return false; + } + + private: + LLLiveFile& mLiveFile; + }; + +} + +void LLLiveFile::addToEventTimer() +{ + impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod); +} + +void LLLiveFile::setRefreshPeriod(F32 seconds) +{ + if (seconds < 0.f) + { + seconds = -seconds; + } + impl.mRefreshPeriod = seconds; +} + diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 0e5b91c9b8..4b7d60d654 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -1,354 +1,354 @@ -/** - * @file llmemory.cpp - * @brief Very special memory allocation/deallocation stuff here - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - - -#include "llthread.h" - -#if defined(LL_WINDOWS) -# include -#elif defined(LL_DARWIN) -# include -# include -# include -#include -#elif LL_LINUX -# include -# include -#endif - -#include "llmemory.h" - -#include "llsys.h" -#include "llframetimer.h" -#include "lltrace.h" -#include "llerror.h" -//---------------------------------------------------------------------------- - -//static -U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); -U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0); -static LLTrace::SampleStatHandle sAllocatedMem("allocated_mem", "active memory in use by application"); -static LLTrace::SampleStatHandle sVirtualMem("virtual_mem", "virtual memory assigned to application"); -U32Kilobytes LLMemory::sAllocatedMemInKB(0); -U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); -U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); - -void ll_assert_aligned_func(uintptr_t ptr,U32 alignment) -{ -#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) - //do not check - return; -#else - #ifdef SHOW_ASSERT - // Redundant, place to set breakpoints. - if (ptr%alignment!=0) - { - LL_WARNS() << "alignment check failed" << LL_ENDL; - } - llassert(ptr%alignment==0); - #endif -#endif -} - -//static -void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) -{ - sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size); -} - -//static -void LLMemory::updateMemoryInfo() -{ - LL_PROFILE_ZONE_SCOPED -#if LL_WINDOWS - PROCESS_MEMORY_COUNTERS counters; - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) - { - LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; - return ; - } - - sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); - sample(sAllocatedMem, sAllocatedMemInKB); - sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); - sample(sVirtualMem, sAllocatedPageSizeInKB); - - U32Kilobytes avail_phys, avail_virtual; - LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; - sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); - - if(sMaxPhysicalMemInKB > sAllocatedMemInKB) - { - sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; - } - else - { - sAvailPhysicalMemInKB = U32Kilobytes(0); - } - -#elif defined(LL_DARWIN) - task_vm_info info; - mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; - // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint. - if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast(&info), &infoCount) == KERN_SUCCESS) - { - // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of - // memory that the memory manager has committed for a running process", which is rss. - sAllocatedPageSizeInKB = U32Bytes(info.resident_size); - - // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size - // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. - // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals. - // - // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value - // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. - // - // (On Windows, we use WorkingSetSize.) - sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - - // Total installed and available physical memory are properties of the host, not just our process. - vm_statistics64_data_t vmstat; - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; - mach_port_t host = mach_host_self(); - vm_size_t page_size; - host_page_size(host, &page_size); - kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast(&vmstat), &count); - if (result == KERN_SUCCESS) { - // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' - // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. - sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); - sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - -#else - //not valid for other systems for now. - sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); - sMaxPhysicalMemInKB = U64Bytes(U32_MAX); - sAvailPhysicalMemInKB = U64Bytes(U32_MAX); -#endif - - return ; -} - -// -//this function is to test if there is enough space with the size in the virtual address space. -//it does not do any real allocation -//if success, it returns the address where the memory chunk can fit in; -//otherwise it returns NULL. -// -//static -void* LLMemory::tryToAlloc(void* address, U32 size) -{ -#if LL_WINDOWS - address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ; - if(address) - { - if(!VirtualFree(address, 0, MEM_RELEASE)) - { - LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ; - } - } - return address ; -#else - return (void*)0x01 ; //skip checking -#endif -} - -//static -void LLMemory::logMemoryInfo(bool update) -{ - LL_PROFILE_ZONE_SCOPED - if(update) - { - updateMemoryInfo() ; - } - - LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ; - LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ; - LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ; - LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ; -} - -//static -U32Kilobytes LLMemory::getAvailableMemKB() -{ - return sAvailPhysicalMemInKB ; -} - -//static -U32Kilobytes LLMemory::getMaxMemKB() -{ - return sMaxPhysicalMemInKB ; -} - -//static -U32Kilobytes LLMemory::getAllocatedMemKB() -{ - return sAllocatedMemInKB ; -} - -//---------------------------------------------------------------------------- - -#if defined(LL_WINDOWS) - -//static -U64 LLMemory::getCurrentRSS() -{ - PROCESS_MEMORY_COUNTERS counters; - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) - { - LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; - return 0; - } - - return counters.WorkingSetSize; -} - -#elif defined(LL_DARWIN) - -// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1) -// { -// LL_WARNS() << "Couldn't get page size" << LL_ENDL; -// return 0; -// } else { -// return page_size; -// } -// } - -U64 LLMemory::getCurrentRSS() -{ - U64 residentSize = 0; - mach_task_basic_info_data_t basicInfo; - mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT; - if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) - { - residentSize = basicInfo.resident_size; - // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size. - // basicInfo.virtual_size is not what we want. - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - - return residentSize; -} - -#elif defined(LL_LINUX) - -U64 LLMemory::getCurrentRSS() -{ - struct rusage usage; - - if (getrusage(RUSAGE_SELF, &usage) != 0) { - // Error handling code could be here - return 0; - } - - // ru_maxrss (since Linux 2.6.32) - // This is the maximum resident set size used (in kilobytes). - return usage.ru_maxrss * 1024; -} - -#else - -U64 LLMemory::getCurrentRSS() -{ - return 0; -} - -#endif - -//-------------------------------------------------------------------- - -#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) - -#include - -struct mem_info { - std::map memory_info; - LLMutex mutex; - - static mem_info& get() { - static mem_info instance; - return instance; - } - -private: - mem_info(){} -}; - -void* ll_aligned_malloc_fallback( size_t size, int align ) -{ - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - - unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize; - - void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); - if(NULL == p) { - // call debugger - __asm int 3; - } - DWORD old; - bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); - if(false == Res) { - // call debugger - __asm int 3; - } - - void* ret = (void*)((char*)p + for_alloc-size); - - { - LLMutexLock lock(&mem_info::get().mutex); - mem_info::get().memory_info.insert(std::pair(ret, p)); - } - - - return ret; -} - -void ll_aligned_free_fallback( void* ptr ) -{ - LLMutexLock lock(&mem_info::get().mutex); - VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE); - mem_info::get().memory_info.erase(ptr); -} - -#endif +/** + * @file llmemory.cpp + * @brief Very special memory allocation/deallocation stuff here + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + + +#include "llthread.h" + +#if defined(LL_WINDOWS) +# include +#elif defined(LL_DARWIN) +# include +# include +# include +#include +#elif LL_LINUX +# include +# include +#endif + +#include "llmemory.h" + +#include "llsys.h" +#include "llframetimer.h" +#include "lltrace.h" +#include "llerror.h" +//---------------------------------------------------------------------------- + +//static +U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); +U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0); +static LLTrace::SampleStatHandle sAllocatedMem("allocated_mem", "active memory in use by application"); +static LLTrace::SampleStatHandle sVirtualMem("virtual_mem", "virtual memory assigned to application"); +U32Kilobytes LLMemory::sAllocatedMemInKB(0); +U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); +U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); + +void ll_assert_aligned_func(uintptr_t ptr,U32 alignment) +{ +#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) + //do not check + return; +#else + #ifdef SHOW_ASSERT + // Redundant, place to set breakpoints. + if (ptr%alignment!=0) + { + LL_WARNS() << "alignment check failed" << LL_ENDL; + } + llassert(ptr%alignment==0); + #endif +#endif +} + +//static +void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) +{ + sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size); +} + +//static +void LLMemory::updateMemoryInfo() +{ + LL_PROFILE_ZONE_SCOPED +#if LL_WINDOWS + PROCESS_MEMORY_COUNTERS counters; + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) + { + LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; + return ; + } + + sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); + sample(sAllocatedMem, sAllocatedMemInKB); + sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); + sample(sVirtualMem, sAllocatedPageSizeInKB); + + U32Kilobytes avail_phys, avail_virtual; + LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; + sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); + + if(sMaxPhysicalMemInKB > sAllocatedMemInKB) + { + sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; + } + else + { + sAvailPhysicalMemInKB = U32Kilobytes(0); + } + +#elif defined(LL_DARWIN) + task_vm_info info; + mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; + // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint. + if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast(&info), &infoCount) == KERN_SUCCESS) + { + // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of + // memory that the memory manager has committed for a running process", which is rss. + sAllocatedPageSizeInKB = U32Bytes(info.resident_size); + + // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size + // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. + // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals. + // + // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value + // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. + // + // (On Windows, we use WorkingSetSize.) + sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + + // Total installed and available physical memory are properties of the host, not just our process. + vm_statistics64_data_t vmstat; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + mach_port_t host = mach_host_self(); + vm_size_t page_size; + host_page_size(host, &page_size); + kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast(&vmstat), &count); + if (result == KERN_SUCCESS) { + // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' + // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. + sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); + sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + +#else + //not valid for other systems for now. + sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); + sMaxPhysicalMemInKB = U64Bytes(U32_MAX); + sAvailPhysicalMemInKB = U64Bytes(U32_MAX); +#endif + + return ; +} + +// +//this function is to test if there is enough space with the size in the virtual address space. +//it does not do any real allocation +//if success, it returns the address where the memory chunk can fit in; +//otherwise it returns NULL. +// +//static +void* LLMemory::tryToAlloc(void* address, U32 size) +{ +#if LL_WINDOWS + address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ; + if(address) + { + if(!VirtualFree(address, 0, MEM_RELEASE)) + { + LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ; + } + } + return address ; +#else + return (void*)0x01 ; //skip checking +#endif +} + +//static +void LLMemory::logMemoryInfo(bool update) +{ + LL_PROFILE_ZONE_SCOPED + if(update) + { + updateMemoryInfo() ; + } + + LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ; + LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ; + LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ; + LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ; +} + +//static +U32Kilobytes LLMemory::getAvailableMemKB() +{ + return sAvailPhysicalMemInKB ; +} + +//static +U32Kilobytes LLMemory::getMaxMemKB() +{ + return sMaxPhysicalMemInKB ; +} + +//static +U32Kilobytes LLMemory::getAllocatedMemKB() +{ + return sAllocatedMemInKB ; +} + +//---------------------------------------------------------------------------- + +#if defined(LL_WINDOWS) + +//static +U64 LLMemory::getCurrentRSS() +{ + PROCESS_MEMORY_COUNTERS counters; + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) + { + LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; + return 0; + } + + return counters.WorkingSetSize; +} + +#elif defined(LL_DARWIN) + +// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1) +// { +// LL_WARNS() << "Couldn't get page size" << LL_ENDL; +// return 0; +// } else { +// return page_size; +// } +// } + +U64 LLMemory::getCurrentRSS() +{ + U64 residentSize = 0; + mach_task_basic_info_data_t basicInfo; + mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) + { + residentSize = basicInfo.resident_size; + // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size. + // basicInfo.virtual_size is not what we want. + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + + return residentSize; +} + +#elif defined(LL_LINUX) + +U64 LLMemory::getCurrentRSS() +{ + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage) != 0) { + // Error handling code could be here + return 0; + } + + // ru_maxrss (since Linux 2.6.32) + // This is the maximum resident set size used (in kilobytes). + return usage.ru_maxrss * 1024; +} + +#else + +U64 LLMemory::getCurrentRSS() +{ + return 0; +} + +#endif + +//-------------------------------------------------------------------- + +#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) + +#include + +struct mem_info { + std::map memory_info; + LLMutex mutex; + + static mem_info& get() { + static mem_info instance; + return instance; + } + +private: + mem_info(){} +}; + +void* ll_aligned_malloc_fallback( size_t size, int align ) +{ + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + + unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize; + + void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); + if(NULL == p) { + // call debugger + __asm int 3; + } + DWORD old; + bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); + if(false == Res) { + // call debugger + __asm int 3; + } + + void* ret = (void*)((char*)p + for_alloc-size); + + { + LLMutexLock lock(&mem_info::get().mutex); + mem_info::get().memory_info.insert(std::pair(ret, p)); + } + + + return ret; +} + +void ll_aligned_free_fallback( void* ptr ) +{ + LLMutexLock lock(&mem_info::get().mutex); + VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE); + mem_info::get().memory_info.erase(ptr); +} + +#endif diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index ea360881c6..2c3f66fab8 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -1,418 +1,418 @@ -/** - * @file llmemory.h - * @brief Memory allocation/deallocation header-stuff goes here. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LLMEMORY_H -#define LLMEMORY_H - -#include "linden_common.h" -#include "llunits.h" -#include "stdtypes.h" -#if !LL_WINDOWS -#include -#endif - -class LLMutex ; - -#if LL_WINDOWS && LL_DEBUG -#define LL_CHECK_MEMORY llassert(_CrtCheckMemory()); -#else -#define LL_CHECK_MEMORY -#endif - - -#if LL_WINDOWS -#define LL_ALIGN_OF __alignof -#else -#define LL_ALIGN_OF __align_of__ -#endif - -#if LL_WINDOWS -#define LL_DEFAULT_HEAP_ALIGN 8 -#elif LL_DARWIN -#define LL_DEFAULT_HEAP_ALIGN 16 -#elif LL_LINUX -#define LL_DEFAULT_HEAP_ALIGN 8 -#endif - - -LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment); - -#ifdef SHOW_ASSERT -// This is incredibly expensive - in profiling Windows RWD builds, 30% -// of CPU time was in aligment checks. -//#define ASSERT_ALIGNMENT -#endif - -#ifdef ASSERT_ALIGNMENT -#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment)) -#else -#define ll_assert_aligned(ptr,alignment) -#endif - -#include - -template T* LL_NEXT_ALIGNED_ADDRESS(T* address) -{ - return reinterpret_cast( - (uintptr_t(address) + 0xF) & ~0xF); -} - -template T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) -{ - return reinterpret_cast( - (uintptr_t(address) + 0x3F) & ~0x3F); -} - -#if LL_LINUX || LL_DARWIN - -#define LL_ALIGN_PREFIX(x) -#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x))) - -#elif LL_WINDOWS - -#define LL_ALIGN_PREFIX(x) __declspec(align(x)) -#define LL_ALIGN_POSTFIX(x) - -#else -#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined" -#endif - -#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16) - -#define LL_ALIGN_NEW \ -public: \ - void* operator new(size_t size) \ - { \ - return ll_aligned_malloc_16(size); \ - } \ - \ - void operator delete(void* ptr) \ - { \ - ll_aligned_free_16(ptr); \ - } \ - \ - void* operator new[](size_t size) \ - { \ - return ll_aligned_malloc_16(size); \ - } \ - \ - void operator delete[](void* ptr) \ - { \ - ll_aligned_free_16(ptr); \ - } - - -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ - // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library - // change preprocessor code to: #if 1 && defined(LL_WINDOWS) - -#if 0 && defined(LL_WINDOWS) - void* ll_aligned_malloc_fallback( size_t size, int align ); - void ll_aligned_free_fallback( void* ptr ); -//------------------------------------------------------------------------------------------------ -#else - inline void* ll_aligned_malloc_fallback( size_t size, int align ) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - #if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, align); - #else - char* aligned = NULL; - void* mem = malloc( size + (align - 1) + sizeof(void*) ); - if (mem) - { - aligned = ((char*)mem) + sizeof(void*); - aligned += align - ((uintptr_t)aligned & (align - 1)); - - ((void**)aligned)[-1] = mem; - } - void* ret = aligned; - #endif - LL_PROFILE_ALLOC(ret, size); - return ret; - } - - inline void ll_aligned_free_fallback( void* ptr ) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(ptr); - #if defined(LL_WINDOWS) - _aligned_free(ptr); - #else - if (ptr) - { - free( ((void**)ptr)[-1] ); - } - #endif - } -#endif -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ - -inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; -#if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, 16); -#elif defined(LL_DARWIN) - void* ret = malloc(size); // default osx malloc is 16 byte aligned. -#else - void *ret; - if (0 != posix_memalign(&ret, 16, size)) - return nullptr; -#endif - LL_PROFILE_ALLOC(ret, size); - return ret; -} - -inline void ll_aligned_free_16(void *p) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(p); -#if defined(LL_WINDOWS) - _aligned_free(p); -#elif defined(LL_DARWIN) - return free(p); -#else - free(p); // posix_memalign() is compatible with heap deallocator -#endif -} - -inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(ptr); -#if defined(LL_WINDOWS) - void* ret = _aligned_realloc(ptr, size, 16); -#elif defined(LL_DARWIN) - void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned. -#else - //FIXME: memcpy is SLOW - void* ret = ll_aligned_malloc_16(size); - if (ptr) - { - if (ret) - { - // Only copy the size of the smallest memory block to avoid memory corruption. - memcpy(ret, ptr, llmin(old_size, size)); - } - ll_aligned_free_16(ptr); - } -#endif - LL_PROFILE_ALLOC(ptr, size); - return ret; -} - -inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; -#if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, 32); -#elif defined(LL_DARWIN) - void* ret = ll_aligned_malloc_fallback( size, 32 ); -#else - void *ret; - if (0 != posix_memalign(&ret, 32, size)) - return nullptr; -#endif - LL_PROFILE_ALLOC(ret, size); - return ret; -} - -inline void ll_aligned_free_32(void *p) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(p); -#if defined(LL_WINDOWS) - _aligned_free(p); -#elif defined(LL_DARWIN) - ll_aligned_free_fallback( p ); -#else - free(p); // posix_memalign() is compatible with heap deallocator -#endif -} - -// general purpose dispatch functions that are forced inline so they can compile down to a single call -template -LL_FORCE_INLINE void* ll_aligned_malloc(size_t size) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - void* ret; - if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0) - { - ret = malloc(size); - LL_PROFILE_ALLOC(ret, size); - } - else if (ALIGNMENT == 16) - { - ret = ll_aligned_malloc_16(size); - } - else if (ALIGNMENT == 32) - { - ret = ll_aligned_malloc_32(size); - } - else - { - ret = ll_aligned_malloc_fallback(size, ALIGNMENT); - } - return ret; -} - -template -LL_FORCE_INLINE void ll_aligned_free(void* ptr) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN) - { - LL_PROFILE_FREE(ptr); - free(ptr); - } - else if (ALIGNMENT == 16) - { - ll_aligned_free_16(ptr); - } - else if (ALIGNMENT == 32) - { - return ll_aligned_free_32(ptr); - } - else - { - return ll_aligned_free_fallback(ptr); - } -} - -// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP. -// Source and dest must be 16-byte aligned and size must be multiple of 16. -// -inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - assert(src != NULL); - assert(dst != NULL); - assert(bytes > 0); - assert((bytes % sizeof(F32))== 0); - ll_assert_aligned(src,16); - ll_assert_aligned(dst,16); - - assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src)); - assert(bytes%16==0); - - char* end = dst + bytes; - - if (bytes > 64) - { - - // Find start of 64b aligned area within block - // - void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst); - - //at least 64 bytes before the end of the destination, switch to 16 byte copies - void* end_64 = end-64; - - // Prefetch the head of the 64b area now - // - _mm_prefetch((char*)begin_64, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA); - - // Copy 16b chunks until we're 64b aligned - // - while (dst < begin_64) - { - - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - dst += 16; - src += 16; - } - - // Copy 64b chunks up to your tail - // - // might be good to shmoo the 512b prefetch offset - // (characterize performance for various values) - // - while (dst < end_64) - { - _mm_prefetch((char*)src + 512, _MM_HINT_NTA); - _mm_prefetch((char*)dst + 512, _MM_HINT_NTA); - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16))); - _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32))); - _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48))); - dst += 64; - src += 64; - } - } - - // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies) - // - while (dst < end) - { - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - dst += 16; - src += 16; - } -} - -#ifndef __DEBUG_PRIVATE_MEM__ -#define __DEBUG_PRIVATE_MEM__ 0 -#endif - -class LL_COMMON_API LLMemory -{ -public: - // Return the resident set size of the current process, in bytes. - // Return value is zero if not known. - static U64 getCurrentRSS(); - static void* tryToAlloc(void* address, U32 size); - static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); - static void updateMemoryInfo() ; - static void logMemoryInfo(bool update = false); - - static U32Kilobytes getAvailableMemKB() ; - static U32Kilobytes getMaxMemKB() ; - static U32Kilobytes getAllocatedMemKB() ; -private: - static U32Kilobytes sAvailPhysicalMemInKB ; - static U32Kilobytes sMaxPhysicalMemInKB ; - static U32Kilobytes sAllocatedMemInKB; - static U32Kilobytes sAllocatedPageSizeInKB ; - - static U32Kilobytes sMaxHeapSizeInKB; -}; - -// LLRefCount moved to llrefcount.h - -// LLPointer moved to llpointer.h - -// LLSafeHandle moved to llsafehandle.h - -// LLSingleton moved to llsingleton.h - - - - -#endif +/** + * @file llmemory.h + * @brief Memory allocation/deallocation header-stuff goes here. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LLMEMORY_H +#define LLMEMORY_H + +#include "linden_common.h" +#include "llunits.h" +#include "stdtypes.h" +#if !LL_WINDOWS +#include +#endif + +class LLMutex ; + +#if LL_WINDOWS && LL_DEBUG +#define LL_CHECK_MEMORY llassert(_CrtCheckMemory()); +#else +#define LL_CHECK_MEMORY +#endif + + +#if LL_WINDOWS +#define LL_ALIGN_OF __alignof +#else +#define LL_ALIGN_OF __align_of__ +#endif + +#if LL_WINDOWS +#define LL_DEFAULT_HEAP_ALIGN 8 +#elif LL_DARWIN +#define LL_DEFAULT_HEAP_ALIGN 16 +#elif LL_LINUX +#define LL_DEFAULT_HEAP_ALIGN 8 +#endif + + +LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment); + +#ifdef SHOW_ASSERT +// This is incredibly expensive - in profiling Windows RWD builds, 30% +// of CPU time was in aligment checks. +//#define ASSERT_ALIGNMENT +#endif + +#ifdef ASSERT_ALIGNMENT +#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment)) +#else +#define ll_assert_aligned(ptr,alignment) +#endif + +#include + +template T* LL_NEXT_ALIGNED_ADDRESS(T* address) +{ + return reinterpret_cast( + (uintptr_t(address) + 0xF) & ~0xF); +} + +template T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) +{ + return reinterpret_cast( + (uintptr_t(address) + 0x3F) & ~0x3F); +} + +#if LL_LINUX || LL_DARWIN + +#define LL_ALIGN_PREFIX(x) +#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x))) + +#elif LL_WINDOWS + +#define LL_ALIGN_PREFIX(x) __declspec(align(x)) +#define LL_ALIGN_POSTFIX(x) + +#else +#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined" +#endif + +#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16) + +#define LL_ALIGN_NEW \ +public: \ + void* operator new(size_t size) \ + { \ + return ll_aligned_malloc_16(size); \ + } \ + \ + void operator delete(void* ptr) \ + { \ + ll_aligned_free_16(ptr); \ + } \ + \ + void* operator new[](size_t size) \ + { \ + return ll_aligned_malloc_16(size); \ + } \ + \ + void operator delete[](void* ptr) \ + { \ + ll_aligned_free_16(ptr); \ + } + + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ + // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library + // change preprocessor code to: #if 1 && defined(LL_WINDOWS) + +#if 0 && defined(LL_WINDOWS) + void* ll_aligned_malloc_fallback( size_t size, int align ); + void ll_aligned_free_fallback( void* ptr ); +//------------------------------------------------------------------------------------------------ +#else + inline void* ll_aligned_malloc_fallback( size_t size, int align ) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + #if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, align); + #else + char* aligned = NULL; + void* mem = malloc( size + (align - 1) + sizeof(void*) ); + if (mem) + { + aligned = ((char*)mem) + sizeof(void*); + aligned += align - ((uintptr_t)aligned & (align - 1)); + + ((void**)aligned)[-1] = mem; + } + void* ret = aligned; + #endif + LL_PROFILE_ALLOC(ret, size); + return ret; + } + + inline void ll_aligned_free_fallback( void* ptr ) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(ptr); + #if defined(LL_WINDOWS) + _aligned_free(ptr); + #else + if (ptr) + { + free( ((void**)ptr)[-1] ); + } + #endif + } +#endif +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ + +inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; +#if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, 16); +#elif defined(LL_DARWIN) + void* ret = malloc(size); // default osx malloc is 16 byte aligned. +#else + void *ret; + if (0 != posix_memalign(&ret, 16, size)) + return nullptr; +#endif + LL_PROFILE_ALLOC(ret, size); + return ret; +} + +inline void ll_aligned_free_16(void *p) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(p); +#if defined(LL_WINDOWS) + _aligned_free(p); +#elif defined(LL_DARWIN) + return free(p); +#else + free(p); // posix_memalign() is compatible with heap deallocator +#endif +} + +inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(ptr); +#if defined(LL_WINDOWS) + void* ret = _aligned_realloc(ptr, size, 16); +#elif defined(LL_DARWIN) + void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned. +#else + //FIXME: memcpy is SLOW + void* ret = ll_aligned_malloc_16(size); + if (ptr) + { + if (ret) + { + // Only copy the size of the smallest memory block to avoid memory corruption. + memcpy(ret, ptr, llmin(old_size, size)); + } + ll_aligned_free_16(ptr); + } +#endif + LL_PROFILE_ALLOC(ptr, size); + return ret; +} + +inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; +#if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, 32); +#elif defined(LL_DARWIN) + void* ret = ll_aligned_malloc_fallback( size, 32 ); +#else + void *ret; + if (0 != posix_memalign(&ret, 32, size)) + return nullptr; +#endif + LL_PROFILE_ALLOC(ret, size); + return ret; +} + +inline void ll_aligned_free_32(void *p) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(p); +#if defined(LL_WINDOWS) + _aligned_free(p); +#elif defined(LL_DARWIN) + ll_aligned_free_fallback( p ); +#else + free(p); // posix_memalign() is compatible with heap deallocator +#endif +} + +// general purpose dispatch functions that are forced inline so they can compile down to a single call +template +LL_FORCE_INLINE void* ll_aligned_malloc(size_t size) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + void* ret; + if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0) + { + ret = malloc(size); + LL_PROFILE_ALLOC(ret, size); + } + else if (ALIGNMENT == 16) + { + ret = ll_aligned_malloc_16(size); + } + else if (ALIGNMENT == 32) + { + ret = ll_aligned_malloc_32(size); + } + else + { + ret = ll_aligned_malloc_fallback(size, ALIGNMENT); + } + return ret; +} + +template +LL_FORCE_INLINE void ll_aligned_free(void* ptr) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN) + { + LL_PROFILE_FREE(ptr); + free(ptr); + } + else if (ALIGNMENT == 16) + { + ll_aligned_free_16(ptr); + } + else if (ALIGNMENT == 32) + { + return ll_aligned_free_32(ptr); + } + else + { + return ll_aligned_free_fallback(ptr); + } +} + +// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP. +// Source and dest must be 16-byte aligned and size must be multiple of 16. +// +inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + assert(src != NULL); + assert(dst != NULL); + assert(bytes > 0); + assert((bytes % sizeof(F32))== 0); + ll_assert_aligned(src,16); + ll_assert_aligned(dst,16); + + assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src)); + assert(bytes%16==0); + + char* end = dst + bytes; + + if (bytes > 64) + { + + // Find start of 64b aligned area within block + // + void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst); + + //at least 64 bytes before the end of the destination, switch to 16 byte copies + void* end_64 = end-64; + + // Prefetch the head of the 64b area now + // + _mm_prefetch((char*)begin_64, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA); + + // Copy 16b chunks until we're 64b aligned + // + while (dst < begin_64) + { + + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + dst += 16; + src += 16; + } + + // Copy 64b chunks up to your tail + // + // might be good to shmoo the 512b prefetch offset + // (characterize performance for various values) + // + while (dst < end_64) + { + _mm_prefetch((char*)src + 512, _MM_HINT_NTA); + _mm_prefetch((char*)dst + 512, _MM_HINT_NTA); + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16))); + _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32))); + _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48))); + dst += 64; + src += 64; + } + } + + // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies) + // + while (dst < end) + { + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + dst += 16; + src += 16; + } +} + +#ifndef __DEBUG_PRIVATE_MEM__ +#define __DEBUG_PRIVATE_MEM__ 0 +#endif + +class LL_COMMON_API LLMemory +{ +public: + // Return the resident set size of the current process, in bytes. + // Return value is zero if not known. + static U64 getCurrentRSS(); + static void* tryToAlloc(void* address, U32 size); + static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); + static void updateMemoryInfo() ; + static void logMemoryInfo(bool update = false); + + static U32Kilobytes getAvailableMemKB() ; + static U32Kilobytes getMaxMemKB() ; + static U32Kilobytes getAllocatedMemKB() ; +private: + static U32Kilobytes sAvailPhysicalMemInKB ; + static U32Kilobytes sMaxPhysicalMemInKB ; + static U32Kilobytes sAllocatedMemInKB; + static U32Kilobytes sAllocatedPageSizeInKB ; + + static U32Kilobytes sMaxHeapSizeInKB; +}; + +// LLRefCount moved to llrefcount.h + +// LLPointer moved to llpointer.h + +// LLSafeHandle moved to llsafehandle.h + +// LLSingleton moved to llsingleton.h + + + + +#endif diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp index addee9fdbf..cc258e4609 100644 --- a/indra/llcommon/llmetricperformancetester.cpp +++ b/indra/llcommon/llmetricperformancetester.cpp @@ -1,333 +1,333 @@ -/** - * @file llmetricperformancetester.cpp - * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "indra_constants.h" -#include "llerror.h" -#include "llsdserialize.h" -#include "lltreeiterators.h" -#include "llmetricperformancetester.h" -#include "llfasttimer.h" - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterBasic : static methods and testers management -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ; - -/*static*/ -void LLMetricPerformanceTesterBasic::cleanupClass() -{ - for (name_tester_map_t::value_type& pair : sTesterMap) - { - delete pair.second; - } - sTesterMap.clear() ; -} - -/*static*/ -bool LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) -{ - llassert_always(tester != NULL); - std::string name = tester->getTesterName() ; - if (getTester(name)) - { - LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ; - return false; - } - - sTesterMap.insert(std::make_pair(name, tester)); - return true; -} - -/*static*/ -void LLMetricPerformanceTesterBasic::deleteTester(std::string name) -{ - name_tester_map_t::iterator tester = sTesterMap.find(name); - if (tester != sTesterMap.end()) - { - delete tester->second; - sTesterMap.erase(tester); - } -} - -/*static*/ -LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name) -{ - // Check for the requested metric name - name_tester_map_t::iterator found_it = sTesterMap.find(name) ; - if (found_it != sTesterMap.end()) - { - return found_it->second ; - } - return NULL ; -} - -/*static*/ -// Return true if this metric is requested or if the general default "catch all" metric is requested -bool LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name) -{ - return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME))); -} - -/*static*/ -LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is) -{ - LLSD ret; - LLSD cur; - - while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) - { - for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) - { - std::string label = iter->first; - - LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ; - if(tester) - { - ret[label]["Name"] = iter->second["Name"] ; - - auto num_of_metrics = tester->getNumberOfMetrics() ; - for(size_t index = 0 ; index < num_of_metrics ; index++) - { - ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ; - } - } - } - } - - return ret; -} - -/*static*/ -void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output) -{ - if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters()) - { - return ; - } - - // Open baseline and current target, exit if one is inexistent - llifstream base_is(baseline.c_str()); - llifstream target_is(target.c_str()); - if (!base_is.is_open() || !target_is.is_open()) - { - LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; - base_is.close(); - target_is.close(); - return; - } - - //analyze baseline - LLSD base = analyzeMetricPerformanceLog(base_is); - base_is.close(); - - //analyze current - LLSD current = analyzeMetricPerformanceLog(target_is); - target_is.close(); - - //output comparision - llofstream os(output.c_str()); - - os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n"; - for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap) - { - LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second); - tester->analyzePerformance(&os, &base, ¤t) ; - } - - os.flush(); - os.close(); -} - - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterBasic : Tester instance methods -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) : - mName(name), - mCount(0) -{ - if (mName == std::string()) - { - LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ; - } - - mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ; -} - -LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic() -{ -} - -void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd) -{ - incrementCurrentCount() ; -} - -void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd) -{ - LLTrace::BlockTimer::pushLog(*sd); -} - -void LLMetricPerformanceTesterBasic::outputTestResults() -{ - LLSD sd; - - preOutputTestResults(&sd) ; - outputTestRecord(&sd) ; - postOutputTestResults(&sd) ; -} - -void LLMetricPerformanceTesterBasic::addMetric(std::string str) -{ - mMetricStrings.push_back(str) ; -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) -{ - resetCurrentCount() ; - - std::string current_label = getCurrentLabelName(); - bool in_base = (*base).has(current_label) ; - bool in_current = (*current).has(current_label) ; - - while(in_base || in_current) - { - LLSD::String label = current_label ; - - if(in_base && in_current) - { - *os << llformat("%s\n", label.c_str()) ; - - for(U32 index = 0 ; index < mMetricStrings.size() ; index++) - { - switch((*current)[label][ mMetricStrings[index] ].type()) - { - case LLSD::TypeInteger: - compareTestResults(os, mMetricStrings[index], - (S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ; - break ; - case LLSD::TypeReal: - compareTestResults(os, mMetricStrings[index], - (F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ; - break; - default: - LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ; - } - } - } - - incrementCurrentCount(); - current_label = getCurrentLabelName(); - in_base = (*base).has(current_label) ; - in_current = (*current).has(current_label) ; - } -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) -{ - *os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current, - v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ; -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) -{ - *os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current, - v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ; -} - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterWithSession -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) : - LLMetricPerformanceTesterBasic(name), - mBaseSessionp(NULL), - mCurrentSessionp(NULL) -{ -} - -LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession() -{ - if (mBaseSessionp) - { - delete mBaseSessionp ; - mBaseSessionp = NULL ; - } - if (mCurrentSessionp) - { - delete mCurrentSessionp ; - mCurrentSessionp = NULL ; - } -} - -/*virtual*/ -void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) -{ - // Load the base session - resetCurrentCount() ; - mBaseSessionp = loadTestSession(base) ; - - // Load the current session - resetCurrentCount() ; - mCurrentSessionp = loadTestSession(current) ; - - if (!mBaseSessionp || !mCurrentSessionp) - { - LL_ERRS() << "Error loading test sessions." << LL_ENDL ; - } - - // Compare - compareTestSessions(os) ; - - // Release memory - if (mBaseSessionp) - { - delete mBaseSessionp ; - mBaseSessionp = NULL ; - } - if (mCurrentSessionp) - { - delete mCurrentSessionp ; - mCurrentSessionp = NULL ; - } -} - - -//---------------------------------------------------------------------------------------------- -// LLTestSession -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession() -{ -} - +/** + * @file llmetricperformancetester.cpp + * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "indra_constants.h" +#include "llerror.h" +#include "llsdserialize.h" +#include "lltreeiterators.h" +#include "llmetricperformancetester.h" +#include "llfasttimer.h" + +//---------------------------------------------------------------------------------------------- +// LLMetricPerformanceTesterBasic : static methods and testers management +//---------------------------------------------------------------------------------------------- + +LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ; + +/*static*/ +void LLMetricPerformanceTesterBasic::cleanupClass() +{ + for (name_tester_map_t::value_type& pair : sTesterMap) + { + delete pair.second; + } + sTesterMap.clear() ; +} + +/*static*/ +bool LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) +{ + llassert_always(tester != NULL); + std::string name = tester->getTesterName() ; + if (getTester(name)) + { + LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ; + return false; + } + + sTesterMap.insert(std::make_pair(name, tester)); + return true; +} + +/*static*/ +void LLMetricPerformanceTesterBasic::deleteTester(std::string name) +{ + name_tester_map_t::iterator tester = sTesterMap.find(name); + if (tester != sTesterMap.end()) + { + delete tester->second; + sTesterMap.erase(tester); + } +} + +/*static*/ +LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name) +{ + // Check for the requested metric name + name_tester_map_t::iterator found_it = sTesterMap.find(name) ; + if (found_it != sTesterMap.end()) + { + return found_it->second ; + } + return NULL ; +} + +/*static*/ +// Return true if this metric is requested or if the general default "catch all" metric is requested +bool LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name) +{ + return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME))); +} + +/*static*/ +LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is) +{ + LLSD ret; + LLSD cur; + + while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) + { + for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) + { + std::string label = iter->first; + + LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ; + if(tester) + { + ret[label]["Name"] = iter->second["Name"] ; + + auto num_of_metrics = tester->getNumberOfMetrics() ; + for(size_t index = 0 ; index < num_of_metrics ; index++) + { + ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ; + } + } + } + } + + return ret; +} + +/*static*/ +void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output) +{ + if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters()) + { + return ; + } + + // Open baseline and current target, exit if one is inexistent + llifstream base_is(baseline.c_str()); + llifstream target_is(target.c_str()); + if (!base_is.is_open() || !target_is.is_open()) + { + LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; + base_is.close(); + target_is.close(); + return; + } + + //analyze baseline + LLSD base = analyzeMetricPerformanceLog(base_is); + base_is.close(); + + //analyze current + LLSD current = analyzeMetricPerformanceLog(target_is); + target_is.close(); + + //output comparision + llofstream os(output.c_str()); + + os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n"; + for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap) + { + LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second); + tester->analyzePerformance(&os, &base, ¤t) ; + } + + os.flush(); + os.close(); +} + + +//---------------------------------------------------------------------------------------------- +// LLMetricPerformanceTesterBasic : Tester instance methods +//---------------------------------------------------------------------------------------------- + +LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) : + mName(name), + mCount(0) +{ + if (mName == std::string()) + { + LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ; + } + + mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ; +} + +LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic() +{ +} + +void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd) +{ + incrementCurrentCount() ; +} + +void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd) +{ + LLTrace::BlockTimer::pushLog(*sd); +} + +void LLMetricPerformanceTesterBasic::outputTestResults() +{ + LLSD sd; + + preOutputTestResults(&sd) ; + outputTestRecord(&sd) ; + postOutputTestResults(&sd) ; +} + +void LLMetricPerformanceTesterBasic::addMetric(std::string str) +{ + mMetricStrings.push_back(str) ; +} + +/*virtual*/ +void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) +{ + resetCurrentCount() ; + + std::string current_label = getCurrentLabelName(); + bool in_base = (*base).has(current_label) ; + bool in_current = (*current).has(current_label) ; + + while(in_base || in_current) + { + LLSD::String label = current_label ; + + if(in_base && in_current) + { + *os << llformat("%s\n", label.c_str()) ; + + for(U32 index = 0 ; index < mMetricStrings.size() ; index++) + { + switch((*current)[label][ mMetricStrings[index] ].type()) + { + case LLSD::TypeInteger: + compareTestResults(os, mMetricStrings[index], + (S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ; + break ; + case LLSD::TypeReal: + compareTestResults(os, mMetricStrings[index], + (F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ; + break; + default: + LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ; + } + } + } + + incrementCurrentCount(); + current_label = getCurrentLabelName(); + in_base = (*base).has(current_label) ; + in_current = (*current).has(current_label) ; + } +} + +/*virtual*/ +void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) +{ + *os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current, + v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ; +} + +/*virtual*/ +void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) +{ + *os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current, + v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ; +} + +//---------------------------------------------------------------------------------------------- +// LLMetricPerformanceTesterWithSession +//---------------------------------------------------------------------------------------------- + +LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) : + LLMetricPerformanceTesterBasic(name), + mBaseSessionp(NULL), + mCurrentSessionp(NULL) +{ +} + +LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession() +{ + if (mBaseSessionp) + { + delete mBaseSessionp ; + mBaseSessionp = NULL ; + } + if (mCurrentSessionp) + { + delete mCurrentSessionp ; + mCurrentSessionp = NULL ; + } +} + +/*virtual*/ +void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) +{ + // Load the base session + resetCurrentCount() ; + mBaseSessionp = loadTestSession(base) ; + + // Load the current session + resetCurrentCount() ; + mCurrentSessionp = loadTestSession(current) ; + + if (!mBaseSessionp || !mCurrentSessionp) + { + LL_ERRS() << "Error loading test sessions." << LL_ENDL ; + } + + // Compare + compareTestSessions(os) ; + + // Release memory + if (mBaseSessionp) + { + delete mBaseSessionp ; + mBaseSessionp = NULL ; + } + if (mCurrentSessionp) + { + delete mCurrentSessionp ; + mCurrentSessionp = NULL ; + } +} + + +//---------------------------------------------------------------------------------------------- +// LLTestSession +//---------------------------------------------------------------------------------------------- + +LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession() +{ +} + diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h index 8989e657ce..78abd53602 100644 --- a/indra/llcommon/llmetricperformancetester.h +++ b/indra/llcommon/llmetricperformancetester.h @@ -1,215 +1,215 @@ -/** - * @file llmetricperformancetester.h - * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_METRICPERFORMANCETESTER_H -#define LL_METRICPERFORMANCETESTER_H - -char const* const DEFAULT_METRIC_NAME = "metric"; - -/** - * @class LLMetricPerformanceTesterBasic - * @brief Performance Metric Base Class - */ -class LL_COMMON_API LLMetricPerformanceTesterBasic -{ -public: - /** - * @brief Creates a basic tester instance. - * @param[in] name - Unique string identifying this tester instance. - */ - LLMetricPerformanceTesterBasic(std::string name); - virtual ~LLMetricPerformanceTesterBasic(); - - /** - * @return Returns true if the instance has been added to the tester map. - * Need to be tested after creation of a tester instance so to know if the tester is correctly handled. - * A tester might not be added to the map if another tester with the same name already exists. - */ - bool isValid() const { return mValidInstance; } - - /** - * @brief Write a set of test results to the log LLSD. - */ - void outputTestResults() ; - - /** - * @brief Compare the test results. - * By default, compares the test results against the baseline one by one, item by item, - * in the increasing order of the LLSD record counter, starting from the first one. - */ - virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; - - static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ; - - /** - * @return Returns the number of the test metrics in this tester instance. - */ - auto getNumberOfMetrics() const { return mMetricStrings.size() ;} - /** - * @return Returns the metric name at index - * @param[in] index - Index on the list of metrics managed by this tester instance. - */ - std::string getMetricName(size_t index) const { return mMetricStrings[index] ;} - -protected: - /** - * @return Returns the name of this tester instance. - */ - std::string getTesterName() const { return mName ;} - - /** - * @brief Insert a new metric to be managed by this tester instance. - * @param[in] str - Unique string identifying the new metric. - */ - void addMetric(std::string str) ; - - /** - * @brief Compare test results, provided in 2 flavors: compare integers and compare floats. - * @param[out] os - Formatted output string holding the compared values. - * @param[in] metric_string - Name of the metric. - * @param[in] v_base - Base value of the metric. - * @param[in] v_current - Current value of the metric. - */ - virtual void compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) ; - virtual void compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) ; - - /** - * @brief Reset internal record count. Count starts with 1. - */ - void resetCurrentCount() { mCount = 1; } - /** - * @brief Increment internal record count. - */ - void incrementCurrentCount() { mCount++; } - /** - * @return Returns the label to be used for the current count. It's "TesterName"-"Count". - */ - std::string getCurrentLabelName() const { return llformat("%s-%d", mName.c_str(), mCount) ;} - - /** - * @brief Write a test record to the LLSD. Implementers need to overload this method. - * @param[out] sd - The LLSD record to store metric data into. - */ - virtual void outputTestRecord(LLSD* sd) = 0 ; - -private: - void preOutputTestResults(LLSD* sd) ; - void postOutputTestResults(LLSD* sd) ; - static LLSD analyzeMetricPerformanceLog(std::istream& is) ; - - std::string mName ; // Name of this tester instance - S32 mCount ; // Current record count - bool mValidInstance; // true if the instance is managed by the map - std::vector< std::string > mMetricStrings ; // Metrics strings - -// Static members managing the collection of testers -public: - // Map of all the tester instances in use - typedef std::map< std::string, LLMetricPerformanceTesterBasic* > name_tester_map_t; - static name_tester_map_t sTesterMap ; - - /** - * @return Returns a pointer to the tester - * @param[in] name - Name of the tester instance queried. - */ - static LLMetricPerformanceTesterBasic* getTester(std::string name) ; - - /** - * @return Delete the named tester from the list - * @param[in] name - Name of the tester instance to delete. - */ - static void deleteTester(std::string name); - - /** - * @return Returns true if that metric *or* the default catch all metric has been requested to be logged - * @param[in] name - Name of the tester queried. - */ - static bool isMetricLogRequested(std::string name); - - /** - * @return Returns true if there's a tester defined, false otherwise. - */ - static bool hasMetricPerformanceTesters() { return !sTesterMap.empty() ;} - /** - * @brief Delete all testers and reset the tester map - */ - static void cleanupClass() ; - -private: - // Add a tester to the map. Returns false if adding fails. - static bool addTester(LLMetricPerformanceTesterBasic* tester) ; -}; - -/** - * @class LLMetricPerformanceTesterWithSession - * @brief Performance Metric Class with custom session - */ -class LL_COMMON_API LLMetricPerformanceTesterWithSession : public LLMetricPerformanceTesterBasic -{ -public: - /** - * @param[in] name - Unique string identifying this tester instance. - */ - LLMetricPerformanceTesterWithSession(std::string name); - virtual ~LLMetricPerformanceTesterWithSession(); - - /** - * @brief Compare the test results. - * This will be loading the base and current sessions and compare them using the virtual - * abstract methods loadTestSession() and compareTestSessions() - */ - virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; - -protected: - /** - * @class LLMetricPerformanceTesterWithSession::LLTestSession - * @brief Defines an interface for the two abstract virtual functions loadTestSession() and compareTestSessions() - */ - class LL_COMMON_API LLTestSession - { - public: - virtual ~LLTestSession() ; - }; - - /** - * @brief Convert an LLSD log into a test session. - * @param[in] log - The LLSD record - * @return Returns the record as a test session - */ - virtual LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) = 0; - - /** - * @brief Compare the base session and the target session. Assumes base and current sessions have been loaded. - * @param[out] os - The comparison result as a standard stream - */ - virtual void compareTestSessions(llofstream* os) = 0; - - LLTestSession* mBaseSessionp; - LLTestSession* mCurrentSessionp; -}; - -#endif - +/** + * @file llmetricperformancetester.h + * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_METRICPERFORMANCETESTER_H +#define LL_METRICPERFORMANCETESTER_H + +char const* const DEFAULT_METRIC_NAME = "metric"; + +/** + * @class LLMetricPerformanceTesterBasic + * @brief Performance Metric Base Class + */ +class LL_COMMON_API LLMetricPerformanceTesterBasic +{ +public: + /** + * @brief Creates a basic tester instance. + * @param[in] name - Unique string identifying this tester instance. + */ + LLMetricPerformanceTesterBasic(std::string name); + virtual ~LLMetricPerformanceTesterBasic(); + + /** + * @return Returns true if the instance has been added to the tester map. + * Need to be tested after creation of a tester instance so to know if the tester is correctly handled. + * A tester might not be added to the map if another tester with the same name already exists. + */ + bool isValid() const { return mValidInstance; } + + /** + * @brief Write a set of test results to the log LLSD. + */ + void outputTestResults() ; + + /** + * @brief Compare the test results. + * By default, compares the test results against the baseline one by one, item by item, + * in the increasing order of the LLSD record counter, starting from the first one. + */ + virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; + + static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ; + + /** + * @return Returns the number of the test metrics in this tester instance. + */ + auto getNumberOfMetrics() const { return mMetricStrings.size() ;} + /** + * @return Returns the metric name at index + * @param[in] index - Index on the list of metrics managed by this tester instance. + */ + std::string getMetricName(size_t index) const { return mMetricStrings[index] ;} + +protected: + /** + * @return Returns the name of this tester instance. + */ + std::string getTesterName() const { return mName ;} + + /** + * @brief Insert a new metric to be managed by this tester instance. + * @param[in] str - Unique string identifying the new metric. + */ + void addMetric(std::string str) ; + + /** + * @brief Compare test results, provided in 2 flavors: compare integers and compare floats. + * @param[out] os - Formatted output string holding the compared values. + * @param[in] metric_string - Name of the metric. + * @param[in] v_base - Base value of the metric. + * @param[in] v_current - Current value of the metric. + */ + virtual void compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) ; + virtual void compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) ; + + /** + * @brief Reset internal record count. Count starts with 1. + */ + void resetCurrentCount() { mCount = 1; } + /** + * @brief Increment internal record count. + */ + void incrementCurrentCount() { mCount++; } + /** + * @return Returns the label to be used for the current count. It's "TesterName"-"Count". + */ + std::string getCurrentLabelName() const { return llformat("%s-%d", mName.c_str(), mCount) ;} + + /** + * @brief Write a test record to the LLSD. Implementers need to overload this method. + * @param[out] sd - The LLSD record to store metric data into. + */ + virtual void outputTestRecord(LLSD* sd) = 0 ; + +private: + void preOutputTestResults(LLSD* sd) ; + void postOutputTestResults(LLSD* sd) ; + static LLSD analyzeMetricPerformanceLog(std::istream& is) ; + + std::string mName ; // Name of this tester instance + S32 mCount ; // Current record count + bool mValidInstance; // true if the instance is managed by the map + std::vector< std::string > mMetricStrings ; // Metrics strings + +// Static members managing the collection of testers +public: + // Map of all the tester instances in use + typedef std::map< std::string, LLMetricPerformanceTesterBasic* > name_tester_map_t; + static name_tester_map_t sTesterMap ; + + /** + * @return Returns a pointer to the tester + * @param[in] name - Name of the tester instance queried. + */ + static LLMetricPerformanceTesterBasic* getTester(std::string name) ; + + /** + * @return Delete the named tester from the list + * @param[in] name - Name of the tester instance to delete. + */ + static void deleteTester(std::string name); + + /** + * @return Returns true if that metric *or* the default catch all metric has been requested to be logged + * @param[in] name - Name of the tester queried. + */ + static bool isMetricLogRequested(std::string name); + + /** + * @return Returns true if there's a tester defined, false otherwise. + */ + static bool hasMetricPerformanceTesters() { return !sTesterMap.empty() ;} + /** + * @brief Delete all testers and reset the tester map + */ + static void cleanupClass() ; + +private: + // Add a tester to the map. Returns false if adding fails. + static bool addTester(LLMetricPerformanceTesterBasic* tester) ; +}; + +/** + * @class LLMetricPerformanceTesterWithSession + * @brief Performance Metric Class with custom session + */ +class LL_COMMON_API LLMetricPerformanceTesterWithSession : public LLMetricPerformanceTesterBasic +{ +public: + /** + * @param[in] name - Unique string identifying this tester instance. + */ + LLMetricPerformanceTesterWithSession(std::string name); + virtual ~LLMetricPerformanceTesterWithSession(); + + /** + * @brief Compare the test results. + * This will be loading the base and current sessions and compare them using the virtual + * abstract methods loadTestSession() and compareTestSessions() + */ + virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; + +protected: + /** + * @class LLMetricPerformanceTesterWithSession::LLTestSession + * @brief Defines an interface for the two abstract virtual functions loadTestSession() and compareTestSessions() + */ + class LL_COMMON_API LLTestSession + { + public: + virtual ~LLTestSession() ; + }; + + /** + * @brief Convert an LLSD log into a test session. + * @param[in] log - The LLSD record + * @return Returns the record as a test session + */ + virtual LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) = 0; + + /** + * @brief Compare the base session and the target session. Assumes base and current sessions have been loaded. + * @param[out] os - The comparison result as a standard stream + */ + virtual void compareTestSessions(llofstream* os) = 0; + + LLTestSession* mBaseSessionp; + LLTestSession* mCurrentSessionp; +}; + +#endif + diff --git a/indra/llcommon/llmortician.cpp b/indra/llcommon/llmortician.cpp index 7ba023f052..578d72388c 100644 --- a/indra/llcommon/llmortician.cpp +++ b/indra/llcommon/llmortician.cpp @@ -1,106 +1,106 @@ -/** - * @file llmortician.cpp - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llmortician.h" - -#include - -std::list LLMortician::sGraveyard; - -bool LLMortician::sDestroyImmediate = false; - -LLMortician::~LLMortician() -{ - sGraveyard.remove(this); -} - -size_t LLMortician::logClass(std::stringstream &str) -{ - auto size = sGraveyard.size(); - str << "Mortician graveyard count: " << size; - str << " Zealous: " << (sDestroyImmediate ? "True" : "False"); - if (size == 0) - { - return size; - } - str << " Output:\n"; - std::list::iterator iter = sGraveyard.begin(); - std::list::iterator end = sGraveyard.end(); - while (iter!=end) - { - LLMortician* dead = *iter; - iter++; - // Be as detailed and safe as possible to figure out issues - str << "Pointer: " << dead; - if (dead) - { - try - { - str << " Is dead: " << (dead->isDead() ? "True" : "False"); - str << " Name: " << typeid(*dead).name(); - } - catch (...) - { - - } - } - str << "\n"; - } - str << "--------------------------------------------"; - return size; -} - -void LLMortician::updateClass() -{ - while (!sGraveyard.empty()) - { - LLMortician* dead = sGraveyard.front(); - delete dead; - } -} - -void LLMortician::die() -{ - // It is valid to call die() more than once on something that hasn't died yet - if (sDestroyImmediate) - { - // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes). - mIsDead = true; - delete this; - return; - } - else if (!mIsDead) - { - mIsDead = true; - sGraveyard.push_back(this); - } -} - -// static -void LLMortician::setZealous(bool b) -{ - sDestroyImmediate = b; -} +/** + * @file llmortician.cpp + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llmortician.h" + +#include + +std::list LLMortician::sGraveyard; + +bool LLMortician::sDestroyImmediate = false; + +LLMortician::~LLMortician() +{ + sGraveyard.remove(this); +} + +size_t LLMortician::logClass(std::stringstream &str) +{ + auto size = sGraveyard.size(); + str << "Mortician graveyard count: " << size; + str << " Zealous: " << (sDestroyImmediate ? "True" : "False"); + if (size == 0) + { + return size; + } + str << " Output:\n"; + std::list::iterator iter = sGraveyard.begin(); + std::list::iterator end = sGraveyard.end(); + while (iter!=end) + { + LLMortician* dead = *iter; + iter++; + // Be as detailed and safe as possible to figure out issues + str << "Pointer: " << dead; + if (dead) + { + try + { + str << " Is dead: " << (dead->isDead() ? "True" : "False"); + str << " Name: " << typeid(*dead).name(); + } + catch (...) + { + + } + } + str << "\n"; + } + str << "--------------------------------------------"; + return size; +} + +void LLMortician::updateClass() +{ + while (!sGraveyard.empty()) + { + LLMortician* dead = sGraveyard.front(); + delete dead; + } +} + +void LLMortician::die() +{ + // It is valid to call die() more than once on something that hasn't died yet + if (sDestroyImmediate) + { + // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes). + mIsDead = true; + delete this; + return; + } + else if (!mIsDead) + { + mIsDead = true; + sGraveyard.push_back(this); + } +} + +// static +void LLMortician::setZealous(bool b) +{ + sDestroyImmediate = b; +} diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index 4aea4e0292..b2d81fa1c5 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -1,56 +1,56 @@ -/** - * @file llmortician.h - * @brief Base class for delayed deletions. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLMORTICIAN_H -#define LLMORTICIAN_H - -#include "stdtypes.h" -#include - -class LL_COMMON_API LLMortician -{ -public: - LLMortician() { mIsDead = false; } - static auto graveyardCount() { return sGraveyard.size(); }; - static size_t logClass(std::stringstream &str); - static void updateClass(); - virtual ~LLMortician(); - void die(); - bool isDead() { return mIsDead; } - - // sets destroy immediate true - static void setZealous(bool b); - -private: - static bool sDestroyImmediate; - - bool mIsDead; - - static std::list sGraveyard; -}; - -#endif - +/** + * @file llmortician.h + * @brief Base class for delayed deletions. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLMORTICIAN_H +#define LLMORTICIAN_H + +#include "stdtypes.h" +#include + +class LL_COMMON_API LLMortician +{ +public: + LLMortician() { mIsDead = false; } + static auto graveyardCount() { return sGraveyard.size(); }; + static size_t logClass(std::stringstream &str); + static void updateClass(); + virtual ~LLMortician(); + void die(); + bool isDead() { return mIsDead; } + + // sets destroy immediate true + static void setZealous(bool b); + +private: + static bool sDestroyImmediate; + + bool mIsDead; + + static std::list sGraveyard; +}; + +#endif + diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index a7c2817e2f..7bdc391459 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -1,418 +1,418 @@ -/** - * @file llmutex.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llmutex.h" -#include "llthread.h" -#include "lltimer.h" - - -//--------------------------------------------------------------------- -// -// LLMutex -// -LLMutex::LLMutex() : - mCount(0) -{ -} - -LLMutex::~LLMutex() -{ -} - -void LLMutex::lock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(isSelfLocked()) - { //redundant lock - mCount++; - return; - } - - mMutex.lock(); - -#if MUTEX_DEBUG - // Have to have the lock before we can access the debug info - auto id = LLThread::currentID(); - if (mIsLocked[id]) - LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = true; -#endif - - mLockingThread = LLThread::currentID(); -} - -void LLMutex::unlock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mCount > 0) - { //not the root unlock - mCount--; - return; - } - -#if MUTEX_DEBUG - // Access the debug info while we have the lock - auto id = LLThread::currentID(); - if (!mIsLocked[id]) - LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = false; -#endif - - mLockingThread = LLThread::id_t(); - mMutex.unlock(); -} - -bool LLMutex::isLocked() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (!mMutex.try_lock()) - { - return true; - } - else - { - mMutex.unlock(); - return false; - } -} - -bool LLMutex::isSelfLocked() -{ - return mLockingThread == LLThread::currentID(); -} - -LLThread::id_t LLMutex::lockingThread() const -{ - return mLockingThread; -} - -bool LLMutex::trylock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (isSelfLocked()) - { //redundant lock - mCount++; - return true; - } - - if (!mMutex.try_lock()) - { - return false; - } - -#if MUTEX_DEBUG - // Have to have the lock before we can access the debug info - auto id = LLThread::currentID(); - if (mIsLocked[id]) - LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = true; -#endif - - mLockingThread = LLThread::currentID(); - return true; -} - - -//--------------------------------------------------------------------- -// -// LLSharedMutex -// -LLSharedMutex::LLSharedMutex() -: mLockingThreads(2) // Reserve 2 slots in the map hash table -, mIsShared(false) -{ -} - -bool LLSharedMutex::isLocked() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - std::lock_guard lock(mLockMutex); - - return !mLockingThreads.empty(); -} - -bool LLSharedMutex::isThreadLocked() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - std::lock_guard lock(mLockMutex); - - const_iterator it = mLockingThreads.find(current_thread); - return it != mLockingThreads.end(); -} - -void LLSharedMutex::lockShared() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - - mLockMutex.lock(); - iterator it = mLockingThreads.find(current_thread); - if (it != mLockingThreads.end()) - { - it->second++; - } - else - { - // Acquire the mutex immediately if the mutex is not locked exclusively - // or enter a locking state if the mutex is already locked exclusively - mLockMutex.unlock(); - mSharedMutex.lock_shared(); - mLockMutex.lock(); - // Continue after acquiring the mutex - mLockingThreads.emplace(std::make_pair(current_thread, 1)); - mIsShared = true; - } - mLockMutex.unlock(); -} - -void LLSharedMutex::lockExclusive() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - - mLockMutex.lock(); - iterator it = mLockingThreads.find(current_thread); - if (it != mLockingThreads.end()) - { - if (mIsShared) - { - // The mutex is already locked in the current thread - // but this lock is SHARED (not EXCLISIVE) - // We can't lock it again, the lock stays shared - // This can lead to a collision (theoretically) - llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE"); - } - it->second++; - } - else - { - // Acquire the mutex immediately if mLockingThreads is empty - // or enter a locking state if mLockingThreads is not empty - mLockMutex.unlock(); - mSharedMutex.lock(); - mLockMutex.lock(); - // Continue after acquiring the mutex (and possible quitting the locking state) - mLockingThreads.emplace(std::make_pair(current_thread, 1)); - mIsShared = false; - } - mLockMutex.unlock(); -} - -bool LLSharedMutex::trylockShared() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - std::lock_guard lock(mLockMutex); - - iterator it = mLockingThreads.find(current_thread); - if (it != mLockingThreads.end()) - { - it->second++; - } - else - { - if (!mSharedMutex.try_lock_shared()) - return false; - - mLockingThreads.emplace(std::make_pair(current_thread, 1)); - mIsShared = true; - } - - return true; -} - -bool LLSharedMutex::trylockExclusive() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - std::lock_guard lock(mLockMutex); - - if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) - { - mLockingThreads.begin()->second++; - } - else - { - if (!mSharedMutex.try_lock()) - return false; - - mLockingThreads.emplace(std::make_pair(current_thread, 1)); - mIsShared = false; - } - - return true; -} - -void LLSharedMutex::unlockShared() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - std::lock_guard lock(mLockMutex); - - iterator it = mLockingThreads.find(current_thread); - if (it != mLockingThreads.end()) - { - if (it->second > 1) - { - it->second--; - } - else - { - mLockingThreads.erase(it); - mSharedMutex.unlock_shared(); - } - } -} - -void LLSharedMutex::unlockExclusive() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - LLThread::id_t current_thread = LLThread::currentID(); - std::lock_guard lock(mLockMutex); - - iterator it = mLockingThreads.find(current_thread); - if (it != mLockingThreads.end()) - { - if (it->second > 1) - { - it->second--; - } - else - { - mLockingThreads.erase(it); - mSharedMutex.unlock(); - } - } -} - - -//--------------------------------------------------------------------- -// -// LLCondition -// -LLCondition::LLCondition() : - LLMutex() -{ -} - -LLCondition::~LLCondition() -{ -} - -void LLCondition::wait() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - std::unique_lock< std::mutex > lock(mMutex); - mCond.wait(lock); -} - -void LLCondition::signal() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mCond.notify_one(); -} - -void LLCondition::broadcast() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mCond.notify_all(); -} - - -//--------------------------------------------------------------------- -// -// LLMutexTrylock -// -LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) - : mMutex(mutex), - mLocked(false) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mMutex) - mLocked = mMutex->trylock(); -} - -LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) - : mMutex(mutex), - mLocked(false) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (!mMutex) - return; - - for (U32 i = 0; i < aTries; ++i) - { - mLocked = mMutex->trylock(); - if (mLocked) - break; - ms_sleep(delay_ms); - } -} - -LLMutexTrylock::~LLMutexTrylock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mMutex && mLocked) - mMutex->unlock(); -} - - -//--------------------------------------------------------------------- -// -// LLScopedLock -// -LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(mutex) - { - mutex->lock(); - mLocked = true; - } - else - { - mLocked = false; - } -} - -LLScopedLock::~LLScopedLock() -{ - unlock(); -} - -void LLScopedLock::unlock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(mLocked) - { - mMutex->unlock(); - mLocked = false; - } -} - -//============================================================================ +/** + * @file llmutex.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llmutex.h" +#include "llthread.h" +#include "lltimer.h" + + +//--------------------------------------------------------------------- +// +// LLMutex +// +LLMutex::LLMutex() : + mCount(0) +{ +} + +LLMutex::~LLMutex() +{ +} + +void LLMutex::lock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(isSelfLocked()) + { //redundant lock + mCount++; + return; + } + + mMutex.lock(); + +#if MUTEX_DEBUG + // Have to have the lock before we can access the debug info + auto id = LLThread::currentID(); + if (mIsLocked[id]) + LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = true; +#endif + + mLockingThread = LLThread::currentID(); +} + +void LLMutex::unlock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mCount > 0) + { //not the root unlock + mCount--; + return; + } + +#if MUTEX_DEBUG + // Access the debug info while we have the lock + auto id = LLThread::currentID(); + if (!mIsLocked[id]) + LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = false; +#endif + + mLockingThread = LLThread::id_t(); + mMutex.unlock(); +} + +bool LLMutex::isLocked() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (!mMutex.try_lock()) + { + return true; + } + else + { + mMutex.unlock(); + return false; + } +} + +bool LLMutex::isSelfLocked() +{ + return mLockingThread == LLThread::currentID(); +} + +LLThread::id_t LLMutex::lockingThread() const +{ + return mLockingThread; +} + +bool LLMutex::trylock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (isSelfLocked()) + { //redundant lock + mCount++; + return true; + } + + if (!mMutex.try_lock()) + { + return false; + } + +#if MUTEX_DEBUG + // Have to have the lock before we can access the debug info + auto id = LLThread::currentID(); + if (mIsLocked[id]) + LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = true; +#endif + + mLockingThread = LLThread::currentID(); + return true; +} + + +//--------------------------------------------------------------------- +// +// LLSharedMutex +// +LLSharedMutex::LLSharedMutex() +: mLockingThreads(2) // Reserve 2 slots in the map hash table +, mIsShared(false) +{ +} + +bool LLSharedMutex::isLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::lock_guard lock(mLockMutex); + + return !mLockingThreads.empty(); +} + +bool LLSharedMutex::isThreadLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + const_iterator it = mLockingThreads.find(current_thread); + return it != mLockingThreads.end(); +} + +void LLSharedMutex::lockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + // Acquire the mutex immediately if the mutex is not locked exclusively + // or enter a locking state if the mutex is already locked exclusively + mLockMutex.unlock(); + mSharedMutex.lock_shared(); + mLockMutex.lock(); + // Continue after acquiring the mutex + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + mLockMutex.unlock(); +} + +void LLSharedMutex::lockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (mIsShared) + { + // The mutex is already locked in the current thread + // but this lock is SHARED (not EXCLISIVE) + // We can't lock it again, the lock stays shared + // This can lead to a collision (theoretically) + llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE"); + } + it->second++; + } + else + { + // Acquire the mutex immediately if mLockingThreads is empty + // or enter a locking state if mLockingThreads is not empty + mLockMutex.unlock(); + mSharedMutex.lock(); + mLockMutex.lock(); + // Continue after acquiring the mutex (and possible quitting the locking state) + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + mLockMutex.unlock(); +} + +bool LLSharedMutex::trylockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + if (!mSharedMutex.try_lock_shared()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + + return true; +} + +bool LLSharedMutex::trylockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) + { + mLockingThreads.begin()->second++; + } + else + { + if (!mSharedMutex.try_lock()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + + return true; +} + +void LLSharedMutex::unlockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock_shared(); + } + } +} + +void LLSharedMutex::unlockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock(); + } + } +} + + +//--------------------------------------------------------------------- +// +// LLCondition +// +LLCondition::LLCondition() : + LLMutex() +{ +} + +LLCondition::~LLCondition() +{ +} + +void LLCondition::wait() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::unique_lock< std::mutex > lock(mMutex); + mCond.wait(lock); +} + +void LLCondition::signal() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mCond.notify_one(); +} + +void LLCondition::broadcast() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mCond.notify_all(); +} + + +//--------------------------------------------------------------------- +// +// LLMutexTrylock +// +LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) + : mMutex(mutex), + mLocked(false) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mMutex) + mLocked = mMutex->trylock(); +} + +LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) + : mMutex(mutex), + mLocked(false) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (!mMutex) + return; + + for (U32 i = 0; i < aTries; ++i) + { + mLocked = mMutex->trylock(); + if (mLocked) + break; + ms_sleep(delay_ms); + } +} + +LLMutexTrylock::~LLMutexTrylock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mMutex && mLocked) + mMutex->unlock(); +} + + +//--------------------------------------------------------------------- +// +// LLScopedLock +// +LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(mutex) + { + mutex->lock(); + mLocked = true; + } + else + { + mLocked = false; + } +} + +LLScopedLock::~LLScopedLock() +{ + unlock(); +} + +void LLScopedLock::unlock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(mLocked) + { + mMutex->unlock(); + mLocked = false; + } +} + +//============================================================================ diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 898ddab284..6e8cf9643b 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -1,271 +1,271 @@ -/** - * @file llmutex.h - * @brief Base classes for mutex and condition handling. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMUTEX_H -#define LL_LLMUTEX_H - -#include "stdtypes.h" -#include "llthread.h" -#include - -#include "mutex.h" -#include -#include -#include - -//============================================================================ - -//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) -#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles - -#if MUTEX_DEBUG -#include -#endif - -class LL_COMMON_API LLMutex -{ -public: - LLMutex(); - virtual ~LLMutex(); - - void lock(); // blocks - bool trylock(); // non-blocking, returns true if lock held. - void unlock(); // undefined behavior when called on mutex not being held - bool isLocked(); // non-blocking, but does do a lock/unlock so not free - bool isSelfLocked(); //return true if locked in a same thread - LLThread::id_t lockingThread() const; //get ID of locking thread - -protected: - std::mutex mMutex; - mutable U32 mCount; - mutable LLThread::id_t mLockingThread; - -#if MUTEX_DEBUG - std::unordered_map mIsLocked; -#endif -}; - -//============================================================================ - -class LL_COMMON_API LLSharedMutex -{ -public: - LLSharedMutex(); - - bool isLocked() const; - bool isThreadLocked() const; - bool isShared() const { return mIsShared; } - - void lockShared(); - void lockExclusive(); - template void lock(); - - bool trylockShared(); - bool trylockExclusive(); - template bool trylock(); - - void unlockShared(); - void unlockExclusive(); - template void unlock(); - -private: - std::shared_mutex mSharedMutex; - mutable std::mutex mLockMutex; - std::unordered_map mLockingThreads; - bool mIsShared; - - using iterator = std::unordered_map::iterator; - using const_iterator = std::unordered_map::const_iterator; -}; - -template<> -inline void LLSharedMutex::lock() -{ - lockShared(); -} - -template<> -inline void LLSharedMutex::lock() -{ - lockExclusive(); -} - -template<> -inline bool LLSharedMutex::trylock() -{ - return trylockShared(); -} - -template<> -inline bool LLSharedMutex::trylock() -{ - return trylockExclusive(); -} - -template<> -inline void LLSharedMutex::unlock() -{ - unlockShared(); -} - -template<> -inline void LLSharedMutex::unlock() -{ - unlockExclusive(); -} - -// Actually a condition/mutex pair (since each condition needs to be associated with a mutex). -class LL_COMMON_API LLCondition : public LLMutex -{ -public: - LLCondition(); - ~LLCondition(); - - void wait(); // blocks - void signal(); - void broadcast(); - -protected: - std::condition_variable mCond; -}; - -//============================================================================ - -class LLMutexLock -{ -public: - LLMutexLock(LLMutex* mutex) - { - mMutex = mutex; - - if (mMutex) - mMutex->lock(); - } - - ~LLMutexLock() - { - if (mMutex) - mMutex->unlock(); - } - -private: - LLMutex* mMutex; -}; - -//============================================================================ - -template -class LLSharedMutexLockTemplate -{ -public: - LLSharedMutexLockTemplate(LLSharedMutex* mutex) - : mSharedMutex(mutex) - { - if (mSharedMutex) - mSharedMutex->lock(); - } - - ~LLSharedMutexLockTemplate() - { - if (mSharedMutex) - mSharedMutex->unlock(); - } - -private: - LLSharedMutex* mSharedMutex; -}; - -using LLSharedMutexLock = LLSharedMutexLockTemplate; -using LLExclusiveMutexLock = LLSharedMutexLockTemplate; - -//============================================================================ - -// Scoped locking class similar in function to LLMutexLock but uses -// the trylock() method to conditionally acquire lock without -// blocking. Caller resolves the resulting condition by calling -// the isLocked() method and either punts or continues as indicated. -// -// Mostly of interest to callers needing to avoid stalls who can -// guarantee another attempt at a later time. - -class LLMutexTrylock -{ -public: - LLMutexTrylock(LLMutex* mutex); - LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10); - ~LLMutexTrylock(); - - bool isLocked() const - { - return mLocked; - } - -private: - LLMutex* mMutex; - bool mLocked; -}; - -//============================================================================ - -/** -* @class LLScopedLock -* @brief Small class to help lock and unlock mutexes. -* -* The constructor handles the lock, and the destructor handles -* the unlock. Instances of this class are not thread safe. -*/ -class LL_COMMON_API LLScopedLock : private boost::noncopyable -{ -public: - /** - * @brief Constructor which accepts a mutex, and locks it. - * - * @param mutex An allocated mutex. If you pass in NULL, - * this wrapper will not lock. - */ - LLScopedLock(std::mutex* mutex); - - /** - * @brief Destructor which unlocks the mutex if still locked. - */ - ~LLScopedLock(); - - /** - * @brief Check lock. - */ - bool isLocked() const { return mLocked; } - - /** - * @brief This method unlocks the mutex. - */ - void unlock(); - -protected: - bool mLocked; - std::mutex* mMutex; -}; - -#endif // LL_LLMUTEX_H +/** + * @file llmutex.h + * @brief Base classes for mutex and condition handling. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMUTEX_H +#define LL_LLMUTEX_H + +#include "stdtypes.h" +#include "llthread.h" +#include + +#include "mutex.h" +#include +#include +#include + +//============================================================================ + +//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) +#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles + +#if MUTEX_DEBUG +#include +#endif + +class LL_COMMON_API LLMutex +{ +public: + LLMutex(); + virtual ~LLMutex(); + + void lock(); // blocks + bool trylock(); // non-blocking, returns true if lock held. + void unlock(); // undefined behavior when called on mutex not being held + bool isLocked(); // non-blocking, but does do a lock/unlock so not free + bool isSelfLocked(); //return true if locked in a same thread + LLThread::id_t lockingThread() const; //get ID of locking thread + +protected: + std::mutex mMutex; + mutable U32 mCount; + mutable LLThread::id_t mLockingThread; + +#if MUTEX_DEBUG + std::unordered_map mIsLocked; +#endif +}; + +//============================================================================ + +class LL_COMMON_API LLSharedMutex +{ +public: + LLSharedMutex(); + + bool isLocked() const; + bool isThreadLocked() const; + bool isShared() const { return mIsShared; } + + void lockShared(); + void lockExclusive(); + template void lock(); + + bool trylockShared(); + bool trylockExclusive(); + template bool trylock(); + + void unlockShared(); + void unlockExclusive(); + template void unlock(); + +private: + std::shared_mutex mSharedMutex; + mutable std::mutex mLockMutex; + std::unordered_map mLockingThreads; + bool mIsShared; + + using iterator = std::unordered_map::iterator; + using const_iterator = std::unordered_map::const_iterator; +}; + +template<> +inline void LLSharedMutex::lock() +{ + lockShared(); +} + +template<> +inline void LLSharedMutex::lock() +{ + lockExclusive(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockShared(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockExclusive(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + unlockShared(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + unlockExclusive(); +} + +// Actually a condition/mutex pair (since each condition needs to be associated with a mutex). +class LL_COMMON_API LLCondition : public LLMutex +{ +public: + LLCondition(); + ~LLCondition(); + + void wait(); // blocks + void signal(); + void broadcast(); + +protected: + std::condition_variable mCond; +}; + +//============================================================================ + +class LLMutexLock +{ +public: + LLMutexLock(LLMutex* mutex) + { + mMutex = mutex; + + if (mMutex) + mMutex->lock(); + } + + ~LLMutexLock() + { + if (mMutex) + mMutex->unlock(); + } + +private: + LLMutex* mMutex; +}; + +//============================================================================ + +template +class LLSharedMutexLockTemplate +{ +public: + LLSharedMutexLockTemplate(LLSharedMutex* mutex) + : mSharedMutex(mutex) + { + if (mSharedMutex) + mSharedMutex->lock(); + } + + ~LLSharedMutexLockTemplate() + { + if (mSharedMutex) + mSharedMutex->unlock(); + } + +private: + LLSharedMutex* mSharedMutex; +}; + +using LLSharedMutexLock = LLSharedMutexLockTemplate; +using LLExclusiveMutexLock = LLSharedMutexLockTemplate; + +//============================================================================ + +// Scoped locking class similar in function to LLMutexLock but uses +// the trylock() method to conditionally acquire lock without +// blocking. Caller resolves the resulting condition by calling +// the isLocked() method and either punts or continues as indicated. +// +// Mostly of interest to callers needing to avoid stalls who can +// guarantee another attempt at a later time. + +class LLMutexTrylock +{ +public: + LLMutexTrylock(LLMutex* mutex); + LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10); + ~LLMutexTrylock(); + + bool isLocked() const + { + return mLocked; + } + +private: + LLMutex* mMutex; + bool mLocked; +}; + +//============================================================================ + +/** +* @class LLScopedLock +* @brief Small class to help lock and unlock mutexes. +* +* The constructor handles the lock, and the destructor handles +* the unlock. Instances of this class are not thread safe. +*/ +class LL_COMMON_API LLScopedLock : private boost::noncopyable +{ +public: + /** + * @brief Constructor which accepts a mutex, and locks it. + * + * @param mutex An allocated mutex. If you pass in NULL, + * this wrapper will not lock. + */ + LLScopedLock(std::mutex* mutex); + + /** + * @brief Destructor which unlocks the mutex if still locked. + */ + ~LLScopedLock(); + + /** + * @brief Check lock. + */ + bool isLocked() const { return mLocked; } + + /** + * @brief This method unlocks the mutex. + */ + void unlock(); + +protected: + bool mLocked; + std::mutex* mMutex; +}; + +#endif // LL_LLMUTEX_H diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h index bcc6267ec5..0c4cc4c04d 100644 --- a/indra/llcommon/llnametable.h +++ b/indra/llcommon/llnametable.h @@ -1,103 +1,103 @@ -/** - * @file llnametable.h - * @brief LLNameTable class is a table to associate pointers with string names - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNAMETABLE_H -#define LL_LLNAMETABLE_H - -#include - -#include "string_table.h" - -template -class LLNameTable -{ -public: - LLNameTable() - : mNameMap() - { - } - - ~LLNameTable() - { - } - - void addEntry(const std::string& name, DATA data) - { - addEntry(name.c_str(), data); - } - - void addEntry(const char *name, DATA data) - { - char *tablename = gStringTable.addString(name); - mNameMap[tablename] = data; - } - - bool checkName(const std::string& name) const - { - return checkName(name.c_str()); - } - - // "logically const" even though it modifies the global nametable - bool checkName(const char *name) const - { - char *tablename = gStringTable.addString(name); - return mNameMap.find(tablename) != mNameMap.end(); - } - - DATA resolveName(const std::string& name) const - { - return resolveName(name.c_str()); - } - - // "logically const" even though it modifies the global nametable - DATA resolveName(const char *name) const - { - char *tablename = gStringTable.addString(name); - const_iter_t iter = mNameMap.find(tablename); - if (iter != mNameMap.end()) - return iter->second; - else - return 0; - } - - // O(N)! (currently only used in one place... (newsim/llstate.cpp)) - const char *resolveData(const DATA &data) const - { - for (const name_map_t::value_type& pair : mNameMap) - { - if (pair.second == data) - return pair.first; - } - return NULL; - } - - typedef std::map name_map_t; - typedef typename std::map::iterator iter_t; - typedef typename std::map::const_iterator const_iter_t; - name_map_t mNameMap; -}; - -#endif +/** + * @file llnametable.h + * @brief LLNameTable class is a table to associate pointers with string names + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAMETABLE_H +#define LL_LLNAMETABLE_H + +#include + +#include "string_table.h" + +template +class LLNameTable +{ +public: + LLNameTable() + : mNameMap() + { + } + + ~LLNameTable() + { + } + + void addEntry(const std::string& name, DATA data) + { + addEntry(name.c_str(), data); + } + + void addEntry(const char *name, DATA data) + { + char *tablename = gStringTable.addString(name); + mNameMap[tablename] = data; + } + + bool checkName(const std::string& name) const + { + return checkName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + bool checkName(const char *name) const + { + char *tablename = gStringTable.addString(name); + return mNameMap.find(tablename) != mNameMap.end(); + } + + DATA resolveName(const std::string& name) const + { + return resolveName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + DATA resolveName(const char *name) const + { + char *tablename = gStringTable.addString(name); + const_iter_t iter = mNameMap.find(tablename); + if (iter != mNameMap.end()) + return iter->second; + else + return 0; + } + + // O(N)! (currently only used in one place... (newsim/llstate.cpp)) + const char *resolveData(const DATA &data) const + { + for (const name_map_t::value_type& pair : mNameMap) + { + if (pair.second == data) + return pair.first; + } + return NULL; + } + + typedef std::map name_map_t; + typedef typename std::map::iterator iter_t; + typedef typename std::map::const_iterator const_iter_t; + name_map_t mNameMap; +}; + +#endif diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h index a0a0855109..2bdd39aac2 100644 --- a/indra/llcommon/llpriqueuemap.h +++ b/indra/llcommon/llpriqueuemap.h @@ -1,145 +1,145 @@ -/** - * @file llpriqueuemap.h - * @brief Priority queue implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LL_LLPRIQUEUEMAP_H -#define LL_LLPRIQUEUEMAP_H - -#include - -// -// Priority queue, implemented under the hood as a -// map. Needs to be done this way because none of the -// standard STL containers provide a representation -// where it's easy to reprioritize. -// - -template -class LLPQMKey -{ -public: - LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data) - { - } - - bool operator<(const LLPQMKey &b) const - { - if (mPriority > b.mPriority) - { - return true; - } - if (mPriority < b.mPriority) - { - return false; - } - if (mData > b.mData) - { - return true; - } - return false; - } - - F32 mPriority; - DATA mData; -}; - -template -class LLPriQueueMap -{ -public: - typedef typename std::map, DATA_TYPE>::iterator pqm_iter; - typedef std::pair, DATA_TYPE> pqm_pair; - typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority); - typedef F32 (*get_pri_fn)(DATA_TYPE &data); - - - LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri) - { - } - - void push(const F32 priority, DATA_TYPE data) - { -#ifdef _DEBUG - pqm_iter iter = mMap.find(LLPQMKey(priority, data)); - if (iter != mMap.end()) - { - LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; - } -#endif - mMap.insert(pqm_pair(LLPQMKey(priority, data), data)); - } - - bool pop(DATA_TYPE *datap) - { - pqm_iter iter; - iter = mMap.begin(); - if (iter == mMap.end()) - { - return false; - } - *datap = (*(iter)).second; - mMap.erase(iter); - - return true; - } - - void reprioritize(const F32 new_priority, DATA_TYPE data) - { - pqm_iter iter; - F32 cur_priority = mGetPriority(data); - LLPQMKey cur_key(cur_priority, data); - iter = mMap.find(cur_key); - if (iter == mMap.end()) - { - LL_WARNS() << "Data not on priority queue!" << LL_ENDL; - // OK, try iterating through all of the data and seeing if we just screwed up the priority - // somehow. - for (pqm_pair pair : mMap) - { - if (pair.second == data) - { - LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL; - } - } - return; - } - - mMap.erase(iter); - mSetPriority(data, new_priority); - push(new_priority, data); - } - - S32 getLength() const - { - return (S32)mMap.size(); - } - - // Hack: public for use by the transfer manager, ugh. - std::map, DATA_TYPE> mMap; -protected: - void (*mSetPriority)(DATA_TYPE &data, const F32 priority); - F32 (*mGetPriority)(DATA_TYPE &data); -}; - -#endif // LL_LLPRIQUEUEMAP_H +/** + * @file llpriqueuemap.h + * @brief Priority queue implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_LLPRIQUEUEMAP_H +#define LL_LLPRIQUEUEMAP_H + +#include + +// +// Priority queue, implemented under the hood as a +// map. Needs to be done this way because none of the +// standard STL containers provide a representation +// where it's easy to reprioritize. +// + +template +class LLPQMKey +{ +public: + LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data) + { + } + + bool operator<(const LLPQMKey &b) const + { + if (mPriority > b.mPriority) + { + return true; + } + if (mPriority < b.mPriority) + { + return false; + } + if (mData > b.mData) + { + return true; + } + return false; + } + + F32 mPriority; + DATA mData; +}; + +template +class LLPriQueueMap +{ +public: + typedef typename std::map, DATA_TYPE>::iterator pqm_iter; + typedef std::pair, DATA_TYPE> pqm_pair; + typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority); + typedef F32 (*get_pri_fn)(DATA_TYPE &data); + + + LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri) + { + } + + void push(const F32 priority, DATA_TYPE data) + { +#ifdef _DEBUG + pqm_iter iter = mMap.find(LLPQMKey(priority, data)); + if (iter != mMap.end()) + { + LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; + } +#endif + mMap.insert(pqm_pair(LLPQMKey(priority, data), data)); + } + + bool pop(DATA_TYPE *datap) + { + pqm_iter iter; + iter = mMap.begin(); + if (iter == mMap.end()) + { + return false; + } + *datap = (*(iter)).second; + mMap.erase(iter); + + return true; + } + + void reprioritize(const F32 new_priority, DATA_TYPE data) + { + pqm_iter iter; + F32 cur_priority = mGetPriority(data); + LLPQMKey cur_key(cur_priority, data); + iter = mMap.find(cur_key); + if (iter == mMap.end()) + { + LL_WARNS() << "Data not on priority queue!" << LL_ENDL; + // OK, try iterating through all of the data and seeing if we just screwed up the priority + // somehow. + for (pqm_pair pair : mMap) + { + if (pair.second == data) + { + LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL; + } + } + return; + } + + mMap.erase(iter); + mSetPriority(data, new_priority); + push(new_priority, data); + } + + S32 getLength() const + { + return (S32)mMap.size(); + } + + // Hack: public for use by the transfer manager, ugh. + std::map, DATA_TYPE> mMap; +protected: + void (*mSetPriority)(DATA_TYPE &data, const F32 priority); + F32 (*mGetPriority)(DATA_TYPE &data); +}; + +#endif // LL_LLPRIQUEUEMAP_H diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 80413bb841..912e596c3f 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1,1358 +1,1358 @@ -/** - * @file llprocess.cpp - * @brief Utility class for launching, terminating, and tracking the state of processes. - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llprocess.h" -#include "llsdutil.h" -#include "llsdserialize.h" -#include "llsingleton.h" -#include "llstring.h" -#include "stringize.h" -#include "llapr.h" -#include "apr_signal.h" -#include "llevents.h" -#include "llexception.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/***************************************************************************** -* Helpers -*****************************************************************************/ -static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; -static std::string empty; -static LLProcess::Status interpret_status(int status); -static std::string getDesc(const LLProcess::Params& params); - -static std::string whichfile(LLProcess::FILESLOT index) -{ - if (index < LL_ARRAY_SIZE(whichfile_)) - return whichfile_[index]; - return STRINGIZE("file slot " << index); -} - -/** - * Ref-counted "mainloop" listener. As long as there are still outstanding - * LLProcess objects, keep listening on "mainloop" so we can keep polling APR - * for process status. - */ -class LLProcessListener -{ - LOG_CLASS(LLProcessListener); -public: - LLProcessListener(): - mCount(0) - {} - - void addPoll(const LLProcess&) - { - // Unconditionally increment mCount. If it was zero before - // incrementing, listen on "mainloop". - if (mCount++ == 0) - { - LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL; - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1)); - } - } - - void dropPoll(const LLProcess&) - { - // Unconditionally decrement mCount. If it's zero after decrementing, - // stop listening on "mainloop". - if (--mCount == 0) - { - LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL; - mConnection.disconnect(); - } - } - -private: - /// called once per frame by the "mainloop" LLEventPump - bool tick(const LLSD&) - { - // Tell APR to sense whether each registered LLProcess is still - // running and call handle_status() appropriately. We should be able - // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at - // least in APR 1.4.2, testing suggests that even with APR_NOWAIT, - // apr_proc_wait() blocks the caller. We can't have that in the - // viewer. Hence the callback rigmarole. (Once we update APR, it's - // probably worth testing again.) Also -- although there's an - // apr_proc_other_child_refresh() call, i.e. get that information for - // one specific child, it accepts an 'apr_other_child_rec_t*' that's - // mentioned NOWHERE else in the documentation or header files! I - // would use the specific call in LLProcess::getStatus() if I knew - // how. As it is, each call to apr_proc_other_child_refresh_all() will - // call callbacks for ALL still-running child processes. That's why we - // centralize such calls, using "mainloop" to ensure it happens once - // per frame, and refcounting running LLProcess objects to remain - // registered only while needed. - LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL; - apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); - return false; - } - - /// If this object is destroyed before mCount goes to zero, stop - /// listening on "mainloop" anyway. - LLTempBoundListener mConnection; - unsigned mCount; -}; -static LLProcessListener sProcessListener; - -/***************************************************************************** -* WritePipe and ReadPipe -*****************************************************************************/ -LLProcess::BasePipe::~BasePipe() {} -const LLProcess::BasePipe::size_type - // use funky syntax to call max() to avoid blighted max() macros - LLProcess::BasePipe::npos((std::numeric_limits::max)()); - -class WritePipeImpl: public LLProcess::WritePipe -{ - LOG_CLASS(WritePipeImpl); -public: - WritePipeImpl(const std::string& desc, apr_file_t* pipe): - mDesc(desc), - mPipe(pipe), - // Essential to initialize our std::ostream with our special streambuf! - mStream(&mStreambuf) - { - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen(LLEventPump::inventName("WritePipe"), - boost::bind(&WritePipeImpl::tick, this, _1)); - -#if ! LL_WINDOWS - // We can't count on every child process reading everything we try to - // write to it. And if the child terminates with WritePipe data still - // pending, unless we explicitly suppress it, Posix will hit us with - // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means - // APR gets the correct errno, passes it back to us, we log it, etc. - signal(SIGPIPE, SIG_IGN); -#endif - } - - virtual std::ostream& get_ostream() { return mStream; } - virtual size_type size() const { return mStreambuf.size(); } - - bool tick(const LLSD&) - { - typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence; - // If there's anything to send, try to send it. - std::size_t total(mStreambuf.size()), consumed(0); - if (total) - { - const_buffer_sequence bufs = mStreambuf.data(); - // In general, our streambuf might contain a number of different - // physical buffers; iterate over those. - bool keepwriting = true; - for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); - bufi != bufend && keepwriting; ++bufi) - { - // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents - // Although apr_file_write() accepts const void*, we - // manipulate const char* so we can increment the pointer. - const char* remainptr = boost::asio::buffer_cast(*bufi); - std::size_t remainlen = boost::asio::buffer_size(*bufi); - while (remainlen) - { - // Tackle the current buffer in discrete chunks. On - // Windows, we've observed strange failures when trying to - // write big lengths (~1 MB) in a single operation. Even a - // 32K chunk seems too large. At some point along the way - // apr_file_write() returns 11 (Resource temporarily - // unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- - // even though it did write the chunk! Our next write - // attempt retries with the same chunk, resulting in the - // chunk being duplicated at the child end. Using smaller - // chunks is empirically more reliable. - std::size_t towrite((std::min)(remainlen, std::size_t(4*1024))); - apr_size_t written(towrite); - apr_status_t err = apr_file_write(mPipe, remainptr, &written); - // EAGAIN is exactly what we want from a nonblocking pipe. - // Rather than waiting for data, it should return immediately. - if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) - { - LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc - << " got " << err << ":" << LL_ENDL; - ll_apr_warn_status(err); - } - - // 'written' is modified to reflect the number of bytes actually - // written. Make sure we consume those later. (Don't consume them - // now, that would invalidate the buffer iterator sequence!) - consumed += written; - // don't forget to advance to next chunk of current buffer - remainptr += written; - remainlen -= written; - - char msgbuf[512]; - LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite - << " bytes to " << mDesc - << " (original " << total << ")," - << " code " << err << ": " - << apr_strerror(err, msgbuf, sizeof(msgbuf)) - << LL_ENDL; - - // The parent end of this pipe is nonblocking. If we weren't able - // to write everything we wanted, don't keep banging on it -- that - // won't change until the child reads some. Wait for next tick(). - if (written < towrite) - { - keepwriting = false; // break outer loop over buffers too - break; - } - } // next chunk of current buffer - } // next buffer - // In all, we managed to write 'consumed' bytes. Remove them from the - // streambuf so we don't keep trying to send them. This could be - // anywhere from 0 up to mStreambuf.size(); anything we haven't yet - // sent, we'll try again later. - mStreambuf.consume(consumed); - } - - return false; - } - -private: - std::string mDesc; - apr_file_t* mPipe; - LLTempBoundListener mConnection; - boost::asio::streambuf mStreambuf; - std::ostream mStream; -}; - -class ReadPipeImpl: public LLProcess::ReadPipe -{ - LOG_CLASS(ReadPipeImpl); -public: - ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index): - mDesc(desc), - mPipe(pipe), - mIndex(index), - // Essential to initialize our std::istream with our special streambuf! - mStream(&mStreambuf), - mPump("ReadPipe", true), // tweak name as needed to avoid collisions - mLimit(0), - mEOF(false) - { - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen(LLEventPump::inventName("ReadPipe"), - boost::bind(&ReadPipeImpl::tick, this, _1)); - } - - ~ReadPipeImpl() - { - if (mConnection.connected()) - { - mConnection.disconnect(); - } - } - - // Much of the implementation is simply connecting the abstract virtual - // methods with implementation data concealed from the base class. - virtual std::istream& get_istream() { return mStream; } - virtual std::string getline() { return LLProcess::getline(mStream); } - virtual LLEventPump& getPump() { return mPump; } - virtual void setLimit(size_type limit) { mLimit = limit; } - virtual size_type getLimit() const { return mLimit; } - virtual size_type size() const { return mStreambuf.size(); } - - virtual std::string read(size_type len) - { - // Read specified number of bytes into a buffer. - size_type readlen((std::min)(size(), len)); - // Formally, &buffer[0] is invalid for a vector of size() 0. Exit - // early in that situation. - if (! readlen) - return ""; - // Make a buffer big enough. - std::vector buffer(readlen); - mStream.read(&buffer[0], readlen); - // Since we've already clamped 'readlen', we can think of no reason - // why mStream.read() should read fewer than 'readlen' bytes. - // Nonetheless, use the actual retrieved length. - return std::string(&buffer[0], mStream.gcount()); - } - - virtual std::string peek(size_type offset=0, size_type len=npos) const - { - // Constrain caller's offset and len to overlap actual buffer content. - std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); - size_type want_end = (len == npos)? npos : (real_offset + len); - std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end)); - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - return std::string(boost::asio::buffers_begin(cbufs) + real_offset, - boost::asio::buffers_begin(cbufs) + real_end); - } - - virtual size_type find(const std::string& seek, size_type offset=0) const - { - // If we're passing a string of length 1, use find(char), which can - // use an O(n) std::find() rather than the O(n^2) std::search(). - if (seek.length() == 1) - { - return find(seek[0], offset); - } - - // If offset is beyond the whole buffer, can't even construct a valid - // iterator range; can't possibly find the string we seek. - if (offset > mStreambuf.size()) - { - return npos; - } - - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - boost::asio::buffers_iterator - begin(boost::asio::buffers_begin(cbufs)), - end (boost::asio::buffers_end(cbufs)), - found(std::search(begin + offset, end, seek.begin(), seek.end())); - return (found == end)? npos : (found - begin); - } - - virtual size_type find(char seek, size_type offset=0) const - { - // If offset is beyond the whole buffer, can't even construct a valid - // iterator range; can't possibly find the char we seek. - if (offset > mStreambuf.size()) - { - return npos; - } - - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - boost::asio::buffers_iterator - begin(boost::asio::buffers_begin(cbufs)), - end (boost::asio::buffers_end(cbufs)), - found(std::find(begin + offset, end, seek)); - return (found == end)? npos : (found - begin); - } - - bool tick(const LLSD&) - { - // Once we've hit EOF, skip all the rest of this. - if (mEOF) - return false; - - typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; - // Try, every time, to read into our streambuf. In fact, we have no - // idea how much data the child might be trying to send: keep trying - // until we're convinced we've temporarily exhausted the pipe. - enum PipeState { RETRY, EXHAUSTED, CLOSED }; - PipeState state = RETRY; - std::size_t committed(0); - do - { - // attempt to read an arbitrary size - mutable_buffer_sequence bufs = mStreambuf.prepare(4096); - // In general, the mutable_buffer_sequence returned by prepare() might - // contain a number of different physical buffers; iterate over those. - std::size_t tocommit(0); - for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); - bufi != bufend; ++bufi) - { - // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents - std::size_t toread(boost::asio::buffer_size(*bufi)); - apr_size_t gotten(toread); - apr_status_t err = apr_file_read(mPipe, - boost::asio::buffer_cast(*bufi), - &gotten); - // EAGAIN is exactly what we want from a nonblocking pipe. - // Rather than waiting for data, it should return immediately. - if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) - { - // Handle EOF specially: it's part of normal-case processing. - if (err == APR_EOF) - { - LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; - } - else - { - LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc - << " got " << err << ":" << LL_ENDL; - ll_apr_warn_status(err); - } - // Either way, though, we won't need any more tick() calls. - mConnection.disconnect(); - // Ignore any subsequent calls we might get anyway. - mEOF = true; - state = CLOSED; // also break outer retry loop - break; - } - - // 'gotten' was modified to reflect the number of bytes actually - // received. Make sure we commit those later. (Don't commit them - // now, that would invalidate the buffer iterator sequence!) - tocommit += gotten; - LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread - << " bytes from " << mDesc << LL_ENDL; - - // The parent end of this pipe is nonblocking. If we weren't even - // able to fill this buffer, don't loop to try to fill the next -- - // that won't change until the child writes more. Wait for next - // tick(). - if (gotten < toread) - { - // break outer retry loop too - state = EXHAUSTED; - break; - } - } - - // Don't forget to "commit" the data! - mStreambuf.commit(tocommit); - committed += tocommit; - - // state is changed from RETRY when we can't fill any one buffer - // of the mutable_buffer_sequence established by the current - // prepare() call -- whether due to error or not enough bytes. - // That is, if state is still RETRY, we've filled every physical - // buffer in the mutable_buffer_sequence. In that case, for all we - // know, the child might have still more data pending -- go for it! - } while (state == RETRY); - - // Once we recognize that the pipe is closed, make one more call to - // listener. The listener might be waiting for a particular substring - // to arrive, or a particular length of data or something. The event - // with "eof" == true announces that nothing further will arrive, so - // use it or lose it. - if (committed || state == CLOSED) - { - // If we actually received new data, publish it on our LLEventPump - // as advertised. Constrain it by mLimit. But show listener the - // actual accumulated buffer size, regardless of mLimit. - size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); - mPump.post(LLSDMap - ("data", peek(0, datasize)) - ("len", LLSD::Integer(mStreambuf.size())) - ("slot", LLSD::Integer(mIndex)) - ("name", whichfile(mIndex)) - ("desc", mDesc) - ("eof", state == CLOSED)); - } - - return false; - } - -private: - std::string mDesc; - apr_file_t* mPipe; - LLProcess::FILESLOT mIndex; - LLTempBoundListener mConnection; - boost::asio::streambuf mStreambuf; - std::istream mStream; - LLEventStream mPump; - size_type mLimit; - bool mEOF; -}; - -/***************************************************************************** -* LLProcess itself -*****************************************************************************/ -/// Need an exception to avoid constructing an invalid LLProcess object, but -/// internal use only -struct LLProcessError: public LLException -{ - LLProcessError(const std::string& msg): LLException(msg) {} -}; - -LLProcessPtr LLProcess::create(const LLSDOrParams& params) -{ - try - { - return LLProcessPtr(new LLProcess(params)); - } - catch (const LLProcessError& e) - { - LL_WARNS("LLProcess") << e.what() << LL_ENDL; - - // If caller is requesting an event on process termination, send one - // indicating bad launch. This may prevent someone waiting forever for - // a termination post that can't arrive because the child never - // started. - if (params.postend.isProvided()) - { - LLEventPumps::instance().obtain(params.postend) - .post(LLSDMap - // no "id" - ("desc", getDesc(params)) - ("state", LLProcess::UNSTARTED) - // no "data" - ("string", e.what()) - ); - } - - return LLProcessPtr(); - } -} - -/// Call an apr function returning apr_status_t. On failure, log warning and -/// throw LLProcessError mentioning the function call that produced that -/// result. -#define chkapr(func) \ - if (ll_apr_warn_status(func)) \ - throw LLProcessError(#func " failed") - -LLProcess::LLProcess(const LLSDOrParams& params): - mAutokill(params.autokill), - // Because 'autokill' originally meant both 'autokill' and 'attached', to - // preserve existing semantics, we promise that mAttached defaults to the - // same setting as mAutokill. - mAttached(params.attached.isProvided()? params.attached : params.autokill), - mPool(NULL), - mPipes(NSLOTS) -{ - // Hmm, when you construct a ptr_vector with a size, it merely reserves - // space, it doesn't actually make it that big. Explicitly make it bigger. - // Because of ptr_vector's odd semantics, have to push_back(0) the right - // number of times! resize() wants to default-construct new BasePipe - // instances, which fails because it's pure virtual. But because of the - // constructor call, these push_back() calls should require no new - // allocation. - for (size_t i = 0; i < mPipes.capacity(); ++i) - mPipes.push_back(0); - - if (! params.validateBlock(true)) - { - LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n" - << LLSDNotationStreamer(params)))); - } - - mPostend = params.postend; - - apr_pool_create(&mPool, gAPRPoolp); - if (!mPool) - { - LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); - } - - apr_procattr_t *procattr = NULL; - chkapr(apr_procattr_create(&procattr, mPool)); - - // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to - // constrain the set of handles passed to the child process. Before we - // changed to APR, the Windows implementation of LLProcessLauncher called - // CreateProcess(bInheritHandles=false), meaning to pass NO open handles - // to the child process. Now that we support pipes, though, we must allow - // apr_proc_create() to pass bInheritHandles=true. But without taking - // special pains, that causes trouble in a number of ways, due to the fact - // that the viewer is constantly opening and closing files -- most of - // which CreateProcess() passes to every child process! -#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET) - // Our special preprocessor symbol isn't even defined -- wrong APR - LL_WARNS("LLProcess") << "This version of APR lacks Linden " - << "apr_procattr_constrain_handle_set() extension" << LL_ENDL; -#else - chkapr(apr_procattr_constrain_handle_set(procattr, 1)); -#endif - - // For which of stdin, stdout, stderr should we create a pipe to the - // child? In the viewer, there are only a couple viable - // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx - // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's - // blocking on the child end but nonblocking at the viewer end - // (APR_CHILD_BLOCK). - // Other major options could include explicitly creating a single APR pipe - // and passing it as both stdout and stderr (apr_procattr_child_out_set(), - // apr_procattr_child_err_set()), or accepting a filename, opening it and - // passing that apr_file_t (simple <, >, 2> redirect emulation). - std::vector select; - for (const FileParam& fparam : params.files) - { - // Every iteration, we're going to append an item to 'select'. At the - // top of the loop, its size() is, in effect, an index. Use that to - // pick a string description for messages. - std::string which(whichfile(FILESLOT(select.size()))); - if (fparam.type().empty()) // inherit our file descriptor - { - select.push_back(APR_NO_PIPE); - } - else if (fparam.type() == "pipe") // anonymous pipe - { - if (! fparam.name().empty()) - { - LL_WARNS("LLProcess") << "For " << params.executable() - << ": internal names for reusing pipes ('" - << fparam.name() << "' for " << which - << ") are not yet supported -- creating distinct pipe" - << LL_ENDL; - } - // The viewer can't block for anything: the parent end MUST be - // nonblocking. As the APR documentation itself points out, it - // makes very little sense to set nonblocking I/O for the child - // end of a pipe: only a specially-written child could deal with - // that. - select.push_back(APR_CHILD_BLOCK); - } - else - { - LLTHROW(LLProcessError(STRINGIZE("For " << params.executable() - << ": unsupported FileParam for " << which - << ": type='" << fparam.type() - << "', name='" << fparam.name() << "'"))); - } - } - // By default, pass APR_NO_PIPE for unspecified slots. - while (select.size() < NSLOTS) - { - select.push_back(APR_NO_PIPE); - } - chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); - - // Thumbs down on implicitly invoking the shell to invoke the child. From - // our point of view, the other major alternative to APR_PROGRAM_PATH - // would be APR_PROGRAM_ENV: still copy environment, but require full - // executable pathname. I don't see a downside to searching the PATH, - // though: if our caller wants (e.g.) a specific Python interpreter, s/he - // can still pass the full pathname. - chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); - // YES, do extra work if necessary to report child exec() failures back to - // parent process. - chkapr(apr_procattr_error_check_set(procattr, 1)); - // Do not start a non-autokill child in detached state. On Posix - // platforms, this setting attempts to daemonize the new child, closing - // std handles and the like, and that's a bit more detachment than we - // want. autokill=false just means not to implicitly kill the child when - // the parent terminates! -// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1)); - - if (mAutokill) - { -#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) - // Our special preprocessor symbol isn't even defined -- wrong APR - LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; -#elif ! APR_HAS_PROCATTR_AUTOKILL_SET - // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to - // return APR_ENOTIMPL. -#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero - ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1)); -#endif - } - - // In preparation for calling apr_proc_create(), we collect a number of - // const char* pointers obtained from std::string::c_str(). Turns out - // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al. - // guarantee that converting to the wrapped type (std::string in our - // case), e.g. by calling operator(), returns a reference to *the same - // instance* of the wrapped type that's stored in our Block subclass. - // That's important! We know 'params' persists throughout this method - // call; but without that guarantee, we'd have to assume that converting - // one of its members to std::string might return a different (temp) - // instance. Capturing the c_str() from a temporary std::string is Bad Bad - // Bad. But armed with this knowledge, when you see params.cwd().c_str(), - // grit your teeth and smile and carry on. - - if (params.cwd.isProvided()) - { - chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str())); - } - - // create an argv vector for the child process - std::vector argv; - - // Add the executable path. See above remarks about c_str(). - argv.push_back(params.executable().c_str()); - - // Add arguments. See above remarks about c_str(). - for (const std::string& arg : params.args) - { - argv.push_back(arg.c_str()); - } - - // terminate with a null pointer - argv.push_back(NULL); - - // Launch! The NULL would be the environment block, if we were passing - // one. Hand-expand chkapr() macro so we can fill in the actual command - // string instead of the variable names. - if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, - mPool))) - { - LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); - } - - // arrange to call status_callback() - apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, - mPool); - // and make sure we poll it once per "mainloop" tick - sProcessListener.addPoll(*this); - mStatus.mState = RUNNING; - - mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')'); - LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL; - - // Unless caller explicitly turned off autokill (child should persist), - // take steps to terminate the child. This is all suspenders-and-belt: in - // theory our destructor should kill an autokill child, but in practice - // that doesn't always work (e.g. VWR-21538). - if (mAutokill) - { -/*==========================================================================*| - // NO: There may be an APR bug, not sure -- but at least on Mac, when - // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently - // either our own PID is getting into the list of processes to kill() - // (unlikely), or somehow one of those PIDs is getting zeroed first, - // so that kill() sends SIGTERM to the whole process group -- this - // process included. I'd have to build and link with a debug version - // of APR to know for sure. It's too bad: this mechanism would be just - // right for dealing with static autokill LLProcessPtr variables, - // which aren't destroyed until after APR is no longer available. - - // Tie the lifespan of this child process to the lifespan of our APR - // pool: on destruction of the pool, forcibly kill the process. Tell - // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use - // SIGKILL. - apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); -|*==========================================================================*/ - - // On Windows, associate the new child process with our Job Object. - autokill(); - } - - // Instantiate the proper pipe I/O machinery - // want to be able to point to apr_proc_t::in, out, err by index - typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr; - static apr_proc_file_ptr members[] = - { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err }; - for (size_t i = 0; i < NSLOTS; ++i) - { - if (select[i] != APR_CHILD_BLOCK) - continue; - std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i)))); - apr_file_t* pipe(mProcess.*(members[i])); - if (i == STDIN) - { - mPipes.replace(i, new WritePipeImpl(desc, pipe)); - } - else - { - mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i))); - } - // Removed temporaily for Xcode 7 build tests: error was: - // "error: expression with side effects will be evaluated despite - // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]"" - //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() - // << "('" << desc << "')" << LL_ENDL; - } -} - -// Helper to obtain a description string, given a Params block -static std::string getDesc(const LLProcess::Params& params) -{ - // If caller specified a description string, by all means use it. - if (params.desc.isProvided()) - return params.desc; - - // Caller didn't say. Use the executable name -- but use just the filename - // part. On Mac, for instance, full pathnames get cumbersome. - return LLProcess::basename(params.executable); -} - -//static -std::string LLProcess::basename(const std::string& path) -{ - // If there are Linden utility functions to manipulate pathnames, I - // haven't found them -- and for this usage, Boost.Filesystem seems kind - // of heavyweight. - std::string::size_type delim = path.find_last_of("\\/"); - // If path contains no pathname delimiters, return the whole thing. - if (delim == std::string::npos) - return path; - - // Return just the part beyond the last delimiter. - return path.substr(delim + 1); -} - -LLProcess::~LLProcess() -{ - // In the Linden viewer, there's at least one static LLProcessPtr. Its - // destructor will be called *after* ll_cleanup_apr(). In such a case, - // unregistering is pointless (and fatal!) -- and kill(), which also - // relies on APR, is impossible. - if (! gAPRPoolp) - return; - - // Only in state RUNNING are we registered for callback. In UNSTARTED we - // haven't yet registered. And since receiving the callback is the only - // way we detect child termination, we only change from state RUNNING at - // the same time we unregister. - if (mStatus.mState == RUNNING) - { - // We're still registered for a callback: unregister. Do it before - // we even issue the kill(): even if kill() somehow prompted an - // instantaneous callback (unlikely), this object is going away! Any - // information updated in this object by such a callback is no longer - // available to any consumer anyway. - apr_proc_other_child_unregister(this); - // One less LLProcess to poll for - sProcessListener.dropPoll(*this); - } - - if (mAttached) - { - kill("destructor"); - } - - if (mPool) - { - apr_pool_destroy(mPool); - mPool = NULL; - } -} - -bool LLProcess::kill(const std::string& who) -{ - if (isRunning()) - { - LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL; - -#if LL_WINDOWS - int sig = -1; -#else // Posix - int sig = SIGTERM; -#endif - - ll_apr_warn_status(apr_proc_kill(&mProcess, sig)); - } - - return ! isRunning(); -} - -//static -bool LLProcess::kill(const LLProcessPtr& p, const std::string& who) -{ - if (! p) - return true; // process dead! (was never running) - return p->kill(who); -} - -bool LLProcess::isRunning() const -{ - return getStatus().mState == RUNNING; -} - -//static -bool LLProcess::isRunning(const LLProcessPtr& p) -{ - if (! p) - return false; - return p->isRunning(); -} - -LLProcess::Status LLProcess::getStatus() const -{ - return mStatus; -} - -//static -LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p) -{ - if (! p) - { - // default-constructed Status has mState == UNSTARTED - return Status(); - } - return p->getStatus(); -} - -std::string LLProcess::getStatusString() const -{ - return getStatusString(getStatus()); -} - -std::string LLProcess::getStatusString(const Status& status) const -{ - return getStatusString(mDesc, status); -} - -//static -std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p) -{ - if (! p) - { - // default-constructed Status has mState == UNSTARTED - return getStatusString(desc, Status()); - } - return desc + " " + p->getStatusString(); -} - -//static -std::string LLProcess::getStatusString(const std::string& desc, const Status& status) -{ - if (status.mState == UNSTARTED) - return desc + " was never launched"; - - if (status.mState == RUNNING) - return desc + " running"; - - if (status.mState == EXITED) - return STRINGIZE(desc << " exited with code " << status.mData); - - if (status.mState == KILLED) -#if LL_WINDOWS - return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); -#else - return STRINGIZE(desc << " killed by signal " << status.mData - << " (" << apr_signal_description_get(status.mData) << ")"); -#endif - - return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); -} - -// Classic-C-style APR callback -void LLProcess::status_callback(int reason, void* data, int status) -{ - // Our only role is to bounce this static method call back into object - // space. - static_cast(data)->handle_status(reason, status); -} - -#define tabent(symbol) { symbol, #symbol } -static struct ReasonCode -{ - int code; - const char* name; -} reasons[] = -{ - tabent(APR_OC_REASON_DEATH), - tabent(APR_OC_REASON_UNWRITABLE), - tabent(APR_OC_REASON_RESTART), - tabent(APR_OC_REASON_UNREGISTER), - tabent(APR_OC_REASON_LOST), - tabent(APR_OC_REASON_RUNNING) -}; -#undef tabent - -// Object-oriented callback -void LLProcess::handle_status(int reason, int status) -{ - { - // This odd appearance of LL_DEBUGS is just to bracket a lookup that will - // only be performed if in fact we're going to produce the log message. - LL_DEBUGS("LLProcess") << empty; - std::string reason_str; - for (const ReasonCode& rcp : reasons) - { - if (reason == rcp.code) - { - reason_str = rcp.name; - break; - } - } - if (reason_str.empty()) - { - reason_str = STRINGIZE("unknown reason " << reason); - } - LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL; - } - - if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST)) - { - // We're only interested in the call when the child terminates. - return; - } - - // Somewhat oddly, APR requires that you explicitly unregister even when - // it already knows the child has terminated. We must pass the same 'data' - // pointer as for the register() call, which was our 'this'. - apr_proc_other_child_unregister(this); - // don't keep polling for a terminated process - sProcessListener.dropPoll(*this); - // We overload mStatus.mState to indicate whether the child is registered - // for APR callback: only RUNNING means registered. Track that we've - // unregistered. We know the child has terminated; might be EXITED or - // KILLED; refine below. - mStatus.mState = EXITED; - - // Make last-gasp calls for each of the ReadPipes we have on hand. Since - // they're listening on "mainloop", we can be sure they'll eventually - // collect all pending data from the child. But we want to be able to - // guarantee to our consumer that by the time we post on the "postend" - // LLEventPump, our ReadPipes are already buffering all the data there - // will ever be from the child. That lets the "postend" listener decide - // what to do with that final data. - for (size_t i = 0; i < mPipes.size(); ++i) - { - std::string error; - ReadPipeImpl* ppipe = getPipePtr(error, FILESLOT(i)); - if (ppipe) - { - static LLSD trivial; - ppipe->tick(trivial); - } - } - -// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); - // It's just wrong to call apr_proc_wait() here. The only way APR knows to - // call us with APR_OC_REASON_DEATH is that it's already reaped this child - // process, so calling wait() will only produce "huh?" from the OS. We - // must rely on the status param passed in, which unfortunately comes - // straight from the OS wait() call, which means we have to decode it by - // hand. - mStatus = interpret_status(status); - LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; - - // If caller requested notification on child termination, send it. - if (! mPostend.empty()) - { - LLEventPumps::instance().obtain(mPostend) - .post(LLSDMap - ("id", getProcessID()) - ("desc", mDesc) - ("state", mStatus.mState) - ("data", mStatus.mData) - ("string", getStatusString()) - ); - } -} - -LLProcess::id LLProcess::getProcessID() const -{ - return mProcess.pid; -} - -LLProcess::handle LLProcess::getProcessHandle() const -{ -#if LL_WINDOWS - return mProcess.hproc; -#else - return mProcess.pid; -#endif -} - -std::string LLProcess::getPipeName(FILESLOT) const -{ - // LLProcess::FileParam::type "npipe" is not yet implemented - return ""; -} - -template -PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) -{ - if (slot >= NSLOTS) - { - error = STRINGIZE(mDesc << " has no slot " << slot); - return NULL; - } - if (mPipes.is_null(slot)) - { - error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe"); - return NULL; - } - // Make sure we dynamic_cast in pointer domain so we can test, rather than - // accepting runtime's exception. - PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); - if (! ppipe) - { - error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); - return NULL; - } - - error.clear(); - return ppipe; -} - -template -PIPETYPE& LLProcess::getPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - LLTHROW(NoPipe(error)); - } - return *wp; -} - -template -boost::optional LLProcess::getOptPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - LL_DEBUGS("LLProcess") << error << LL_ENDL; - return boost::optional(); - } - return *wp; -} - -LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) -{ - return getPipe(slot); -} - -boost::optional LLProcess::getOptWritePipe(FILESLOT slot) -{ - return getOptPipe(slot); -} - -LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) -{ - return getPipe(slot); -} - -boost::optional LLProcess::getOptReadPipe(FILESLOT slot) -{ - return getOptPipe(slot); -} - -//static -std::string LLProcess::getline(std::istream& in) -{ - std::string line; - std::getline(in, line); - // Blur the distinction between "\r\n" and plain "\n". std::getline() will - // have eaten the "\n", but we could still end up with a trailing "\r". - std::string::size_type lastpos = line.find_last_not_of("\r"); - if (lastpos != std::string::npos) - { - // Found at least one character that's not a trailing '\r'. SKIP OVER - // IT and erase the rest of the line. - line.erase(lastpos+1); - } - return line; -} - -std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) -{ - if (params.cwd.isProvided()) - { - out << "cd " << LLStringUtil::quote(params.cwd) << ": "; - } - out << LLStringUtil::quote(params.executable); - for (const std::string& arg : params.args) - { - out << ' ' << LLStringUtil::quote(arg); - } - return out; -} - -/***************************************************************************** -* Windows specific -*****************************************************************************/ -#if LL_WINDOWS - -static std::string WindowsErrorString(const std::string& operation); - -void LLProcess::autokill() -{ - // hopefully now handled by apr_procattr_autokill_set() -} - -LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) -{ - // This direct Windows implementation is because we have no access to the - // apr_proc_t struct: we expect it's been destroyed. - if (! h) - return 0; - - DWORD waitresult = WaitForSingleObject(h, 0); - if(waitresult == WAIT_OBJECT_0) - { - // the process has completed. - if (! desc.empty()) - { - DWORD status = 0; - if (! GetExitCodeProcess(h, &status)) - { - LL_WARNS("LLProcess") << desc << " terminated, but " - << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL; - } - { - LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status)) - << LL_ENDL; - } - } - CloseHandle(h); - return 0; - } - - return h; -} - -static LLProcess::Status interpret_status(int status) -{ - LLProcess::Status result; - - // This bit of code is cribbed from apr/threadproc/win32/proc.c, a - // function (unfortunately static) called why_from_exit_code(): - /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how - * this class of failures was determined - */ - if ((status & 0xFFFF0000) == 0xC0000000) - { - result.mState = LLProcess::KILLED; - } - else - { - result.mState = LLProcess::EXITED; - } - result.mData = status; - - return result; -} - -/// GetLastError()/FormatMessage() boilerplate -static std::string WindowsErrorString(const std::string& operation) -{ - auto result = GetLastError(); - return STRINGIZE(operation << " failed (" << result << "): " - << windows_message(result)); -} - -/***************************************************************************** -* Posix specific -*****************************************************************************/ -#else // Mac and linux - -#include -#include -#include -#include - -void LLProcess::autokill() -{ - // What we ought to do here is to: - // 1. create a unique process group and run all autokill children in that - // group (see https://jira.secondlife.com/browse/SWAT-563); - // 2. figure out a way to intercept control when the viewer exits -- - // gracefully or not; - // 3. when the viewer exits, kill off the aforementioned process group. - - // It's point 2 that's troublesome. Although I've seen some signal- - // handling logic in the Posix viewer code, I haven't yet found any bit of - // code that's run no matter how the viewer exits (a try/finally for the - // whole process, as it were). -} - -// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise. -static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL) -{ - LLProcess::Status dummy; - if (! pstatus) - { - // If caller doesn't want to see Status, give us a target anyway so we - // don't have to have a bunch of conditionals. - pstatus = &dummy; - } - - int status = 0; - pid_t wait_result = ::waitpid(pid, &status, WNOHANG); - if (wait_result == pid) - { - *pstatus = interpret_status(status); - return true; - } - if (wait_result == 0) - { - pstatus->mState = LLProcess::RUNNING; - pstatus->mData = 0; - return false; - } - - // Clear caller's Status block; caller must interpret UNSTARTED to mean - // "if this PID was ever valid, it no longer is." - *pstatus = LLProcess::Status(); - - // We've dealt with the success cases: we were able to reap the child - // (wait_result == pid) or it's still running (wait_result == 0). It may - // be that the child terminated but didn't hang around long enough for us - // to reap. In that case we still have no Status to report, but we can at - // least state that it's not running. - if (wait_result == -1 && errno == ECHILD) - { - // No such process -- this may mean we're ignoring SIGCHILD. - return true; - } - - // Uh, should never happen?! - LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned " - << wait_result << "; not meaningful?" << LL_ENDL; - // If caller is looping until this pid terminates, and if we can't find - // out, better to break the loop than to claim it's still running. - return true; -} - -LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) -{ - // This direct Posix implementation is because we have no access to the - // apr_proc_t struct: we expect it's been destroyed. - if (! pid) - return 0; - - // Check whether the process has exited, and reap it if it has. - LLProcess::Status status; - if(reap_pid(pid, &status)) - { - // the process has exited. - if (! desc.empty()) - { - std::string statstr(desc + " apparently terminated: no status available"); - // We don't just pass UNSTARTED to getStatusString() because, in - // the context of reap_pid(), that state has special meaning. - if (status.mState != UNSTARTED) - { - statstr = getStatusString(desc, status); - } - LL_INFOS("LLProcess") << statstr << LL_ENDL; - } - return 0; - } - - return pid; -} - -static LLProcess::Status interpret_status(int status) -{ - LLProcess::Status result; - - if (WIFEXITED(status)) - { - result.mState = LLProcess::EXITED; - result.mData = WEXITSTATUS(status); - } - else if (WIFSIGNALED(status)) - { - result.mState = LLProcess::KILLED; - result.mData = WTERMSIG(status); - } - else // uh, shouldn't happen? - { - result.mState = LLProcess::EXITED; - result.mData = status; // someone else will have to decode - } - - return result; -} - -#endif // Posix +/** + * @file llprocess.cpp + * @brief Utility class for launching, terminating, and tracking the state of processes. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llprocess.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llsingleton.h" +#include "llstring.h" +#include "stringize.h" +#include "llapr.h" +#include "apr_signal.h" +#include "llevents.h" +#include "llexception.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** +* Helpers +*****************************************************************************/ +static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; +static std::string empty; +static LLProcess::Status interpret_status(int status); +static std::string getDesc(const LLProcess::Params& params); + +static std::string whichfile(LLProcess::FILESLOT index) +{ + if (index < LL_ARRAY_SIZE(whichfile_)) + return whichfile_[index]; + return STRINGIZE("file slot " << index); +} + +/** + * Ref-counted "mainloop" listener. As long as there are still outstanding + * LLProcess objects, keep listening on "mainloop" so we can keep polling APR + * for process status. + */ +class LLProcessListener +{ + LOG_CLASS(LLProcessListener); +public: + LLProcessListener(): + mCount(0) + {} + + void addPoll(const LLProcess&) + { + // Unconditionally increment mCount. If it was zero before + // incrementing, listen on "mainloop". + if (mCount++ == 0) + { + LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL; + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1)); + } + } + + void dropPoll(const LLProcess&) + { + // Unconditionally decrement mCount. If it's zero after decrementing, + // stop listening on "mainloop". + if (--mCount == 0) + { + LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL; + mConnection.disconnect(); + } + } + +private: + /// called once per frame by the "mainloop" LLEventPump + bool tick(const LLSD&) + { + // Tell APR to sense whether each registered LLProcess is still + // running and call handle_status() appropriately. We should be able + // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at + // least in APR 1.4.2, testing suggests that even with APR_NOWAIT, + // apr_proc_wait() blocks the caller. We can't have that in the + // viewer. Hence the callback rigmarole. (Once we update APR, it's + // probably worth testing again.) Also -- although there's an + // apr_proc_other_child_refresh() call, i.e. get that information for + // one specific child, it accepts an 'apr_other_child_rec_t*' that's + // mentioned NOWHERE else in the documentation or header files! I + // would use the specific call in LLProcess::getStatus() if I knew + // how. As it is, each call to apr_proc_other_child_refresh_all() will + // call callbacks for ALL still-running child processes. That's why we + // centralize such calls, using "mainloop" to ensure it happens once + // per frame, and refcounting running LLProcess objects to remain + // registered only while needed. + LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL; + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + return false; + } + + /// If this object is destroyed before mCount goes to zero, stop + /// listening on "mainloop" anyway. + LLTempBoundListener mConnection; + unsigned mCount; +}; +static LLProcessListener sProcessListener; + +/***************************************************************************** +* WritePipe and ReadPipe +*****************************************************************************/ +LLProcess::BasePipe::~BasePipe() {} +const LLProcess::BasePipe::size_type + // use funky syntax to call max() to avoid blighted max() macros + LLProcess::BasePipe::npos((std::numeric_limits::max)()); + +class WritePipeImpl: public LLProcess::WritePipe +{ + LOG_CLASS(WritePipeImpl); +public: + WritePipeImpl(const std::string& desc, apr_file_t* pipe): + mDesc(desc), + mPipe(pipe), + // Essential to initialize our std::ostream with our special streambuf! + mStream(&mStreambuf) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("WritePipe"), + boost::bind(&WritePipeImpl::tick, this, _1)); + +#if ! LL_WINDOWS + // We can't count on every child process reading everything we try to + // write to it. And if the child terminates with WritePipe data still + // pending, unless we explicitly suppress it, Posix will hit us with + // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means + // APR gets the correct errno, passes it back to us, we log it, etc. + signal(SIGPIPE, SIG_IGN); +#endif + } + + virtual std::ostream& get_ostream() { return mStream; } + virtual size_type size() const { return mStreambuf.size(); } + + bool tick(const LLSD&) + { + typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence; + // If there's anything to send, try to send it. + std::size_t total(mStreambuf.size()), consumed(0); + if (total) + { + const_buffer_sequence bufs = mStreambuf.data(); + // In general, our streambuf might contain a number of different + // physical buffers; iterate over those. + bool keepwriting = true; + for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend && keepwriting; ++bufi) + { + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + // Although apr_file_write() accepts const void*, we + // manipulate const char* so we can increment the pointer. + const char* remainptr = boost::asio::buffer_cast(*bufi); + std::size_t remainlen = boost::asio::buffer_size(*bufi); + while (remainlen) + { + // Tackle the current buffer in discrete chunks. On + // Windows, we've observed strange failures when trying to + // write big lengths (~1 MB) in a single operation. Even a + // 32K chunk seems too large. At some point along the way + // apr_file_write() returns 11 (Resource temporarily + // unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- + // even though it did write the chunk! Our next write + // attempt retries with the same chunk, resulting in the + // chunk being duplicated at the child end. Using smaller + // chunks is empirically more reliable. + std::size_t towrite((std::min)(remainlen, std::size_t(4*1024))); + apr_size_t written(towrite); + apr_status_t err = apr_file_write(mPipe, remainptr, &written); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + + // 'written' is modified to reflect the number of bytes actually + // written. Make sure we consume those later. (Don't consume them + // now, that would invalidate the buffer iterator sequence!) + consumed += written; + // don't forget to advance to next chunk of current buffer + remainptr += written; + remainlen -= written; + + char msgbuf[512]; + LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite + << " bytes to " << mDesc + << " (original " << total << ")," + << " code " << err << ": " + << apr_strerror(err, msgbuf, sizeof(msgbuf)) + << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't able + // to write everything we wanted, don't keep banging on it -- that + // won't change until the child reads some. Wait for next tick(). + if (written < towrite) + { + keepwriting = false; // break outer loop over buffers too + break; + } + } // next chunk of current buffer + } // next buffer + // In all, we managed to write 'consumed' bytes. Remove them from the + // streambuf so we don't keep trying to send them. This could be + // anywhere from 0 up to mStreambuf.size(); anything we haven't yet + // sent, we'll try again later. + mStreambuf.consume(consumed); + } + + return false; + } + +private: + std::string mDesc; + apr_file_t* mPipe; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::ostream mStream; +}; + +class ReadPipeImpl: public LLProcess::ReadPipe +{ + LOG_CLASS(ReadPipeImpl); +public: + ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index): + mDesc(desc), + mPipe(pipe), + mIndex(index), + // Essential to initialize our std::istream with our special streambuf! + mStream(&mStreambuf), + mPump("ReadPipe", true), // tweak name as needed to avoid collisions + mLimit(0), + mEOF(false) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("ReadPipe"), + boost::bind(&ReadPipeImpl::tick, this, _1)); + } + + ~ReadPipeImpl() + { + if (mConnection.connected()) + { + mConnection.disconnect(); + } + } + + // Much of the implementation is simply connecting the abstract virtual + // methods with implementation data concealed from the base class. + virtual std::istream& get_istream() { return mStream; } + virtual std::string getline() { return LLProcess::getline(mStream); } + virtual LLEventPump& getPump() { return mPump; } + virtual void setLimit(size_type limit) { mLimit = limit; } + virtual size_type getLimit() const { return mLimit; } + virtual size_type size() const { return mStreambuf.size(); } + + virtual std::string read(size_type len) + { + // Read specified number of bytes into a buffer. + size_type readlen((std::min)(size(), len)); + // Formally, &buffer[0] is invalid for a vector of size() 0. Exit + // early in that situation. + if (! readlen) + return ""; + // Make a buffer big enough. + std::vector buffer(readlen); + mStream.read(&buffer[0], readlen); + // Since we've already clamped 'readlen', we can think of no reason + // why mStream.read() should read fewer than 'readlen' bytes. + // Nonetheless, use the actual retrieved length. + return std::string(&buffer[0], mStream.gcount()); + } + + virtual std::string peek(size_type offset=0, size_type len=npos) const + { + // Constrain caller's offset and len to overlap actual buffer content. + std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); + size_type want_end = (len == npos)? npos : (real_offset + len); + std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end)); + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + return std::string(boost::asio::buffers_begin(cbufs) + real_offset, + boost::asio::buffers_begin(cbufs) + real_end); + } + + virtual size_type find(const std::string& seek, size_type offset=0) const + { + // If we're passing a string of length 1, use find(char), which can + // use an O(n) std::find() rather than the O(n^2) std::search(). + if (seek.length() == 1) + { + return find(seek[0], offset); + } + + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the string we seek. + if (offset > mStreambuf.size()) + { + return npos; + } + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::search(begin + offset, end, seek.begin(), seek.end())); + return (found == end)? npos : (found - begin); + } + + virtual size_type find(char seek, size_type offset=0) const + { + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the char we seek. + if (offset > mStreambuf.size()) + { + return npos; + } + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::find(begin + offset, end, seek)); + return (found == end)? npos : (found - begin); + } + + bool tick(const LLSD&) + { + // Once we've hit EOF, skip all the rest of this. + if (mEOF) + return false; + + typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; + // Try, every time, to read into our streambuf. In fact, we have no + // idea how much data the child might be trying to send: keep trying + // until we're convinced we've temporarily exhausted the pipe. + enum PipeState { RETRY, EXHAUSTED, CLOSED }; + PipeState state = RETRY; + std::size_t committed(0); + do + { + // attempt to read an arbitrary size + mutable_buffer_sequence bufs = mStreambuf.prepare(4096); + // In general, the mutable_buffer_sequence returned by prepare() might + // contain a number of different physical buffers; iterate over those. + std::size_t tocommit(0); + for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend; ++bufi) + { + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + std::size_t toread(boost::asio::buffer_size(*bufi)); + apr_size_t gotten(toread); + apr_status_t err = apr_file_read(mPipe, + boost::asio::buffer_cast(*bufi), + &gotten); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + // Handle EOF specially: it's part of normal-case processing. + if (err == APR_EOF) + { + LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; + } + else + { + LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + // Either way, though, we won't need any more tick() calls. + mConnection.disconnect(); + // Ignore any subsequent calls we might get anyway. + mEOF = true; + state = CLOSED; // also break outer retry loop + break; + } + + // 'gotten' was modified to reflect the number of bytes actually + // received. Make sure we commit those later. (Don't commit them + // now, that would invalidate the buffer iterator sequence!) + tocommit += gotten; + LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread + << " bytes from " << mDesc << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't even + // able to fill this buffer, don't loop to try to fill the next -- + // that won't change until the child writes more. Wait for next + // tick(). + if (gotten < toread) + { + // break outer retry loop too + state = EXHAUSTED; + break; + } + } + + // Don't forget to "commit" the data! + mStreambuf.commit(tocommit); + committed += tocommit; + + // state is changed from RETRY when we can't fill any one buffer + // of the mutable_buffer_sequence established by the current + // prepare() call -- whether due to error or not enough bytes. + // That is, if state is still RETRY, we've filled every physical + // buffer in the mutable_buffer_sequence. In that case, for all we + // know, the child might have still more data pending -- go for it! + } while (state == RETRY); + + // Once we recognize that the pipe is closed, make one more call to + // listener. The listener might be waiting for a particular substring + // to arrive, or a particular length of data or something. The event + // with "eof" == true announces that nothing further will arrive, so + // use it or lose it. + if (committed || state == CLOSED) + { + // If we actually received new data, publish it on our LLEventPump + // as advertised. Constrain it by mLimit. But show listener the + // actual accumulated buffer size, regardless of mLimit. + size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); + mPump.post(LLSDMap + ("data", peek(0, datasize)) + ("len", LLSD::Integer(mStreambuf.size())) + ("slot", LLSD::Integer(mIndex)) + ("name", whichfile(mIndex)) + ("desc", mDesc) + ("eof", state == CLOSED)); + } + + return false; + } + +private: + std::string mDesc; + apr_file_t* mPipe; + LLProcess::FILESLOT mIndex; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::istream mStream; + LLEventStream mPump; + size_type mLimit; + bool mEOF; +}; + +/***************************************************************************** +* LLProcess itself +*****************************************************************************/ +/// Need an exception to avoid constructing an invalid LLProcess object, but +/// internal use only +struct LLProcessError: public LLException +{ + LLProcessError(const std::string& msg): LLException(msg) {} +}; + +LLProcessPtr LLProcess::create(const LLSDOrParams& params) +{ + try + { + return LLProcessPtr(new LLProcess(params)); + } + catch (const LLProcessError& e) + { + LL_WARNS("LLProcess") << e.what() << LL_ENDL; + + // If caller is requesting an event on process termination, send one + // indicating bad launch. This may prevent someone waiting forever for + // a termination post that can't arrive because the child never + // started. + if (params.postend.isProvided()) + { + LLEventPumps::instance().obtain(params.postend) + .post(LLSDMap + // no "id" + ("desc", getDesc(params)) + ("state", LLProcess::UNSTARTED) + // no "data" + ("string", e.what()) + ); + } + + return LLProcessPtr(); + } +} + +/// Call an apr function returning apr_status_t. On failure, log warning and +/// throw LLProcessError mentioning the function call that produced that +/// result. +#define chkapr(func) \ + if (ll_apr_warn_status(func)) \ + throw LLProcessError(#func " failed") + +LLProcess::LLProcess(const LLSDOrParams& params): + mAutokill(params.autokill), + // Because 'autokill' originally meant both 'autokill' and 'attached', to + // preserve existing semantics, we promise that mAttached defaults to the + // same setting as mAutokill. + mAttached(params.attached.isProvided()? params.attached : params.autokill), + mPool(NULL), + mPipes(NSLOTS) +{ + // Hmm, when you construct a ptr_vector with a size, it merely reserves + // space, it doesn't actually make it that big. Explicitly make it bigger. + // Because of ptr_vector's odd semantics, have to push_back(0) the right + // number of times! resize() wants to default-construct new BasePipe + // instances, which fails because it's pure virtual. But because of the + // constructor call, these push_back() calls should require no new + // allocation. + for (size_t i = 0; i < mPipes.capacity(); ++i) + mPipes.push_back(0); + + if (! params.validateBlock(true)) + { + LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n" + << LLSDNotationStreamer(params)))); + } + + mPostend = params.postend; + + apr_pool_create(&mPool, gAPRPoolp); + if (!mPool) + { + LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); + } + + apr_procattr_t *procattr = NULL; + chkapr(apr_procattr_create(&procattr, mPool)); + + // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to + // constrain the set of handles passed to the child process. Before we + // changed to APR, the Windows implementation of LLProcessLauncher called + // CreateProcess(bInheritHandles=false), meaning to pass NO open handles + // to the child process. Now that we support pipes, though, we must allow + // apr_proc_create() to pass bInheritHandles=true. But without taking + // special pains, that causes trouble in a number of ways, due to the fact + // that the viewer is constantly opening and closing files -- most of + // which CreateProcess() passes to every child process! +#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden " + << "apr_procattr_constrain_handle_set() extension" << LL_ENDL; +#else + chkapr(apr_procattr_constrain_handle_set(procattr, 1)); +#endif + + // For which of stdin, stdout, stderr should we create a pipe to the + // child? In the viewer, there are only a couple viable + // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx + // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's + // blocking on the child end but nonblocking at the viewer end + // (APR_CHILD_BLOCK). + // Other major options could include explicitly creating a single APR pipe + // and passing it as both stdout and stderr (apr_procattr_child_out_set(), + // apr_procattr_child_err_set()), or accepting a filename, opening it and + // passing that apr_file_t (simple <, >, 2> redirect emulation). + std::vector select; + for (const FileParam& fparam : params.files) + { + // Every iteration, we're going to append an item to 'select'. At the + // top of the loop, its size() is, in effect, an index. Use that to + // pick a string description for messages. + std::string which(whichfile(FILESLOT(select.size()))); + if (fparam.type().empty()) // inherit our file descriptor + { + select.push_back(APR_NO_PIPE); + } + else if (fparam.type() == "pipe") // anonymous pipe + { + if (! fparam.name().empty()) + { + LL_WARNS("LLProcess") << "For " << params.executable() + << ": internal names for reusing pipes ('" + << fparam.name() << "' for " << which + << ") are not yet supported -- creating distinct pipe" + << LL_ENDL; + } + // The viewer can't block for anything: the parent end MUST be + // nonblocking. As the APR documentation itself points out, it + // makes very little sense to set nonblocking I/O for the child + // end of a pipe: only a specially-written child could deal with + // that. + select.push_back(APR_CHILD_BLOCK); + } + else + { + LLTHROW(LLProcessError(STRINGIZE("For " << params.executable() + << ": unsupported FileParam for " << which + << ": type='" << fparam.type() + << "', name='" << fparam.name() << "'"))); + } + } + // By default, pass APR_NO_PIPE for unspecified slots. + while (select.size() < NSLOTS) + { + select.push_back(APR_NO_PIPE); + } + chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); + + // Thumbs down on implicitly invoking the shell to invoke the child. From + // our point of view, the other major alternative to APR_PROGRAM_PATH + // would be APR_PROGRAM_ENV: still copy environment, but require full + // executable pathname. I don't see a downside to searching the PATH, + // though: if our caller wants (e.g.) a specific Python interpreter, s/he + // can still pass the full pathname. + chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + // YES, do extra work if necessary to report child exec() failures back to + // parent process. + chkapr(apr_procattr_error_check_set(procattr, 1)); + // Do not start a non-autokill child in detached state. On Posix + // platforms, this setting attempts to daemonize the new child, closing + // std handles and the like, and that's a bit more detachment than we + // want. autokill=false just means not to implicitly kill the child when + // the parent terminates! +// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1)); + + if (mAutokill) + { +#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; +#elif ! APR_HAS_PROCATTR_AUTOKILL_SET + // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to + // return APR_ENOTIMPL. +#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero + ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1)); +#endif + } + + // In preparation for calling apr_proc_create(), we collect a number of + // const char* pointers obtained from std::string::c_str(). Turns out + // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al. + // guarantee that converting to the wrapped type (std::string in our + // case), e.g. by calling operator(), returns a reference to *the same + // instance* of the wrapped type that's stored in our Block subclass. + // That's important! We know 'params' persists throughout this method + // call; but without that guarantee, we'd have to assume that converting + // one of its members to std::string might return a different (temp) + // instance. Capturing the c_str() from a temporary std::string is Bad Bad + // Bad. But armed with this knowledge, when you see params.cwd().c_str(), + // grit your teeth and smile and carry on. + + if (params.cwd.isProvided()) + { + chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str())); + } + + // create an argv vector for the child process + std::vector argv; + + // Add the executable path. See above remarks about c_str(). + argv.push_back(params.executable().c_str()); + + // Add arguments. See above remarks about c_str(). + for (const std::string& arg : params.args) + { + argv.push_back(arg.c_str()); + } + + // terminate with a null pointer + argv.push_back(NULL); + + // Launch! The NULL would be the environment block, if we were passing + // one. Hand-expand chkapr() macro so we can fill in the actual command + // string instead of the variable names. + if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, + mPool))) + { + LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); + } + + // arrange to call status_callback() + apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, + mPool); + // and make sure we poll it once per "mainloop" tick + sProcessListener.addPoll(*this); + mStatus.mState = RUNNING; + + mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')'); + LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL; + + // Unless caller explicitly turned off autokill (child should persist), + // take steps to terminate the child. This is all suspenders-and-belt: in + // theory our destructor should kill an autokill child, but in practice + // that doesn't always work (e.g. VWR-21538). + if (mAutokill) + { +/*==========================================================================*| + // NO: There may be an APR bug, not sure -- but at least on Mac, when + // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently + // either our own PID is getting into the list of processes to kill() + // (unlikely), or somehow one of those PIDs is getting zeroed first, + // so that kill() sends SIGTERM to the whole process group -- this + // process included. I'd have to build and link with a debug version + // of APR to know for sure. It's too bad: this mechanism would be just + // right for dealing with static autokill LLProcessPtr variables, + // which aren't destroyed until after APR is no longer available. + + // Tie the lifespan of this child process to the lifespan of our APR + // pool: on destruction of the pool, forcibly kill the process. Tell + // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use + // SIGKILL. + apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); +|*==========================================================================*/ + + // On Windows, associate the new child process with our Job Object. + autokill(); + } + + // Instantiate the proper pipe I/O machinery + // want to be able to point to apr_proc_t::in, out, err by index + typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr; + static apr_proc_file_ptr members[] = + { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err }; + for (size_t i = 0; i < NSLOTS; ++i) + { + if (select[i] != APR_CHILD_BLOCK) + continue; + std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i)))); + apr_file_t* pipe(mProcess.*(members[i])); + if (i == STDIN) + { + mPipes.replace(i, new WritePipeImpl(desc, pipe)); + } + else + { + mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i))); + } + // Removed temporaily for Xcode 7 build tests: error was: + // "error: expression with side effects will be evaluated despite + // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]"" + //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() + // << "('" << desc << "')" << LL_ENDL; + } +} + +// Helper to obtain a description string, given a Params block +static std::string getDesc(const LLProcess::Params& params) +{ + // If caller specified a description string, by all means use it. + if (params.desc.isProvided()) + return params.desc; + + // Caller didn't say. Use the executable name -- but use just the filename + // part. On Mac, for instance, full pathnames get cumbersome. + return LLProcess::basename(params.executable); +} + +//static +std::string LLProcess::basename(const std::string& path) +{ + // If there are Linden utility functions to manipulate pathnames, I + // haven't found them -- and for this usage, Boost.Filesystem seems kind + // of heavyweight. + std::string::size_type delim = path.find_last_of("\\/"); + // If path contains no pathname delimiters, return the whole thing. + if (delim == std::string::npos) + return path; + + // Return just the part beyond the last delimiter. + return path.substr(delim + 1); +} + +LLProcess::~LLProcess() +{ + // In the Linden viewer, there's at least one static LLProcessPtr. Its + // destructor will be called *after* ll_cleanup_apr(). In such a case, + // unregistering is pointless (and fatal!) -- and kill(), which also + // relies on APR, is impossible. + if (! gAPRPoolp) + return; + + // Only in state RUNNING are we registered for callback. In UNSTARTED we + // haven't yet registered. And since receiving the callback is the only + // way we detect child termination, we only change from state RUNNING at + // the same time we unregister. + if (mStatus.mState == RUNNING) + { + // We're still registered for a callback: unregister. Do it before + // we even issue the kill(): even if kill() somehow prompted an + // instantaneous callback (unlikely), this object is going away! Any + // information updated in this object by such a callback is no longer + // available to any consumer anyway. + apr_proc_other_child_unregister(this); + // One less LLProcess to poll for + sProcessListener.dropPoll(*this); + } + + if (mAttached) + { + kill("destructor"); + } + + if (mPool) + { + apr_pool_destroy(mPool); + mPool = NULL; + } +} + +bool LLProcess::kill(const std::string& who) +{ + if (isRunning()) + { + LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL; + +#if LL_WINDOWS + int sig = -1; +#else // Posix + int sig = SIGTERM; +#endif + + ll_apr_warn_status(apr_proc_kill(&mProcess, sig)); + } + + return ! isRunning(); +} + +//static +bool LLProcess::kill(const LLProcessPtr& p, const std::string& who) +{ + if (! p) + return true; // process dead! (was never running) + return p->kill(who); +} + +bool LLProcess::isRunning() const +{ + return getStatus().mState == RUNNING; +} + +//static +bool LLProcess::isRunning(const LLProcessPtr& p) +{ + if (! p) + return false; + return p->isRunning(); +} + +LLProcess::Status LLProcess::getStatus() const +{ + return mStatus; +} + +//static +LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return Status(); + } + return p->getStatus(); +} + +std::string LLProcess::getStatusString() const +{ + return getStatusString(getStatus()); +} + +std::string LLProcess::getStatusString(const Status& status) const +{ + return getStatusString(mDesc, status); +} + +//static +std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return getStatusString(desc, Status()); + } + return desc + " " + p->getStatusString(); +} + +//static +std::string LLProcess::getStatusString(const std::string& desc, const Status& status) +{ + if (status.mState == UNSTARTED) + return desc + " was never launched"; + + if (status.mState == RUNNING) + return desc + " running"; + + if (status.mState == EXITED) + return STRINGIZE(desc << " exited with code " << status.mData); + + if (status.mState == KILLED) +#if LL_WINDOWS + return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); +#else + return STRINGIZE(desc << " killed by signal " << status.mData + << " (" << apr_signal_description_get(status.mData) << ")"); +#endif + + return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); +} + +// Classic-C-style APR callback +void LLProcess::status_callback(int reason, void* data, int status) +{ + // Our only role is to bounce this static method call back into object + // space. + static_cast(data)->handle_status(reason, status); +} + +#define tabent(symbol) { symbol, #symbol } +static struct ReasonCode +{ + int code; + const char* name; +} reasons[] = +{ + tabent(APR_OC_REASON_DEATH), + tabent(APR_OC_REASON_UNWRITABLE), + tabent(APR_OC_REASON_RESTART), + tabent(APR_OC_REASON_UNREGISTER), + tabent(APR_OC_REASON_LOST), + tabent(APR_OC_REASON_RUNNING) +}; +#undef tabent + +// Object-oriented callback +void LLProcess::handle_status(int reason, int status) +{ + { + // This odd appearance of LL_DEBUGS is just to bracket a lookup that will + // only be performed if in fact we're going to produce the log message. + LL_DEBUGS("LLProcess") << empty; + std::string reason_str; + for (const ReasonCode& rcp : reasons) + { + if (reason == rcp.code) + { + reason_str = rcp.name; + break; + } + } + if (reason_str.empty()) + { + reason_str = STRINGIZE("unknown reason " << reason); + } + LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL; + } + + if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST)) + { + // We're only interested in the call when the child terminates. + return; + } + + // Somewhat oddly, APR requires that you explicitly unregister even when + // it already knows the child has terminated. We must pass the same 'data' + // pointer as for the register() call, which was our 'this'. + apr_proc_other_child_unregister(this); + // don't keep polling for a terminated process + sProcessListener.dropPoll(*this); + // We overload mStatus.mState to indicate whether the child is registered + // for APR callback: only RUNNING means registered. Track that we've + // unregistered. We know the child has terminated; might be EXITED or + // KILLED; refine below. + mStatus.mState = EXITED; + + // Make last-gasp calls for each of the ReadPipes we have on hand. Since + // they're listening on "mainloop", we can be sure they'll eventually + // collect all pending data from the child. But we want to be able to + // guarantee to our consumer that by the time we post on the "postend" + // LLEventPump, our ReadPipes are already buffering all the data there + // will ever be from the child. That lets the "postend" listener decide + // what to do with that final data. + for (size_t i = 0; i < mPipes.size(); ++i) + { + std::string error; + ReadPipeImpl* ppipe = getPipePtr(error, FILESLOT(i)); + if (ppipe) + { + static LLSD trivial; + ppipe->tick(trivial); + } + } + +// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); + // It's just wrong to call apr_proc_wait() here. The only way APR knows to + // call us with APR_OC_REASON_DEATH is that it's already reaped this child + // process, so calling wait() will only produce "huh?" from the OS. We + // must rely on the status param passed in, which unfortunately comes + // straight from the OS wait() call, which means we have to decode it by + // hand. + mStatus = interpret_status(status); + LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; + + // If caller requested notification on child termination, send it. + if (! mPostend.empty()) + { + LLEventPumps::instance().obtain(mPostend) + .post(LLSDMap + ("id", getProcessID()) + ("desc", mDesc) + ("state", mStatus.mState) + ("data", mStatus.mData) + ("string", getStatusString()) + ); + } +} + +LLProcess::id LLProcess::getProcessID() const +{ + return mProcess.pid; +} + +LLProcess::handle LLProcess::getProcessHandle() const +{ +#if LL_WINDOWS + return mProcess.hproc; +#else + return mProcess.pid; +#endif +} + +std::string LLProcess::getPipeName(FILESLOT) const +{ + // LLProcess::FileParam::type "npipe" is not yet implemented + return ""; +} + +template +PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) +{ + if (slot >= NSLOTS) + { + error = STRINGIZE(mDesc << " has no slot " << slot); + return NULL; + } + if (mPipes.is_null(slot)) + { + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe"); + return NULL; + } + // Make sure we dynamic_cast in pointer domain so we can test, rather than + // accepting runtime's exception. + PIPETYPE* ppipe = dynamic_cast(&mPipes[slot]); + if (! ppipe) + { + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); + return NULL; + } + + error.clear(); + return ppipe; +} + +template +PIPETYPE& LLProcess::getPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LLTHROW(NoPipe(error)); + } + return *wp; +} + +template +boost::optional LLProcess::getOptPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LL_DEBUGS("LLProcess") << error << LL_ENDL; + return boost::optional(); + } + return *wp; +} + +LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptWritePipe(FILESLOT slot) +{ + return getOptPipe(slot); +} + +LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptReadPipe(FILESLOT slot) +{ + return getOptPipe(slot); +} + +//static +std::string LLProcess::getline(std::istream& in) +{ + std::string line; + std::getline(in, line); + // Blur the distinction between "\r\n" and plain "\n". std::getline() will + // have eaten the "\n", but we could still end up with a trailing "\r". + std::string::size_type lastpos = line.find_last_not_of("\r"); + if (lastpos != std::string::npos) + { + // Found at least one character that's not a trailing '\r'. SKIP OVER + // IT and erase the rest of the line. + line.erase(lastpos+1); + } + return line; +} + +std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) +{ + if (params.cwd.isProvided()) + { + out << "cd " << LLStringUtil::quote(params.cwd) << ": "; + } + out << LLStringUtil::quote(params.executable); + for (const std::string& arg : params.args) + { + out << ' ' << LLStringUtil::quote(arg); + } + return out; +} + +/***************************************************************************** +* Windows specific +*****************************************************************************/ +#if LL_WINDOWS + +static std::string WindowsErrorString(const std::string& operation); + +void LLProcess::autokill() +{ + // hopefully now handled by apr_procattr_autokill_set() +} + +LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) +{ + // This direct Windows implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. + if (! h) + return 0; + + DWORD waitresult = WaitForSingleObject(h, 0); + if(waitresult == WAIT_OBJECT_0) + { + // the process has completed. + if (! desc.empty()) + { + DWORD status = 0; + if (! GetExitCodeProcess(h, &status)) + { + LL_WARNS("LLProcess") << desc << " terminated, but " + << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL; + } + { + LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status)) + << LL_ENDL; + } + } + CloseHandle(h); + return 0; + } + + return h; +} + +static LLProcess::Status interpret_status(int status) +{ + LLProcess::Status result; + + // This bit of code is cribbed from apr/threadproc/win32/proc.c, a + // function (unfortunately static) called why_from_exit_code(): + /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how + * this class of failures was determined + */ + if ((status & 0xFFFF0000) == 0xC0000000) + { + result.mState = LLProcess::KILLED; + } + else + { + result.mState = LLProcess::EXITED; + } + result.mData = status; + + return result; +} + +/// GetLastError()/FormatMessage() boilerplate +static std::string WindowsErrorString(const std::string& operation) +{ + auto result = GetLastError(); + return STRINGIZE(operation << " failed (" << result << "): " + << windows_message(result)); +} + +/***************************************************************************** +* Posix specific +*****************************************************************************/ +#else // Mac and linux + +#include +#include +#include +#include + +void LLProcess::autokill() +{ + // What we ought to do here is to: + // 1. create a unique process group and run all autokill children in that + // group (see https://jira.secondlife.com/browse/SWAT-563); + // 2. figure out a way to intercept control when the viewer exits -- + // gracefully or not; + // 3. when the viewer exits, kill off the aforementioned process group. + + // It's point 2 that's troublesome. Although I've seen some signal- + // handling logic in the Posix viewer code, I haven't yet found any bit of + // code that's run no matter how the viewer exits (a try/finally for the + // whole process, as it were). +} + +// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise. +static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL) +{ + LLProcess::Status dummy; + if (! pstatus) + { + // If caller doesn't want to see Status, give us a target anyway so we + // don't have to have a bunch of conditionals. + pstatus = &dummy; + } + + int status = 0; + pid_t wait_result = ::waitpid(pid, &status, WNOHANG); + if (wait_result == pid) + { + *pstatus = interpret_status(status); + return true; + } + if (wait_result == 0) + { + pstatus->mState = LLProcess::RUNNING; + pstatus->mData = 0; + return false; + } + + // Clear caller's Status block; caller must interpret UNSTARTED to mean + // "if this PID was ever valid, it no longer is." + *pstatus = LLProcess::Status(); + + // We've dealt with the success cases: we were able to reap the child + // (wait_result == pid) or it's still running (wait_result == 0). It may + // be that the child terminated but didn't hang around long enough for us + // to reap. In that case we still have no Status to report, but we can at + // least state that it's not running. + if (wait_result == -1 && errno == ECHILD) + { + // No such process -- this may mean we're ignoring SIGCHILD. + return true; + } + + // Uh, should never happen?! + LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned " + << wait_result << "; not meaningful?" << LL_ENDL; + // If caller is looping until this pid terminates, and if we can't find + // out, better to break the loop than to claim it's still running. + return true; +} + +LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) +{ + // This direct Posix implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. + if (! pid) + return 0; + + // Check whether the process has exited, and reap it if it has. + LLProcess::Status status; + if(reap_pid(pid, &status)) + { + // the process has exited. + if (! desc.empty()) + { + std::string statstr(desc + " apparently terminated: no status available"); + // We don't just pass UNSTARTED to getStatusString() because, in + // the context of reap_pid(), that state has special meaning. + if (status.mState != UNSTARTED) + { + statstr = getStatusString(desc, status); + } + LL_INFOS("LLProcess") << statstr << LL_ENDL; + } + return 0; + } + + return pid; +} + +static LLProcess::Status interpret_status(int status) +{ + LLProcess::Status result; + + if (WIFEXITED(status)) + { + result.mState = LLProcess::EXITED; + result.mData = WEXITSTATUS(status); + } + else if (WIFSIGNALED(status)) + { + result.mState = LLProcess::KILLED; + result.mData = WTERMSIG(status); + } + else // uh, shouldn't happen? + { + result.mState = LLProcess::EXITED; + result.mData = status; // someone else will have to decode + } + + return result; +} + +#endif // Posix diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 4a909f601a..39e8113587 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -1,587 +1,587 @@ -/** - * @file llqueuedthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llqueuedthread.h" - -#include - -#include "llstl.h" -#include "lltimer.h" // ms_sleep() -#include "llmutex.h" - -//============================================================================ - -// MAIN THREAD -LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : - LLThread(name), - mIdleThread(true), - mNextHandle(0), - mStarted(false), - mThreaded(threaded), - mRequestQueue(name, 1024 * 1024) -{ - llassert(threaded); // not threaded implementation is deprecated - mMainQueue = LL::WorkQueue::getInstance("mainloop"); - - if (mThreaded) - { - if(should_pause) - { - pause() ; //call this before start the thread. - } - - start(); - } -} - -// MAIN THREAD -LLQueuedThread::~LLQueuedThread() -{ - if (!mThreaded) - { - endThread(); - } - shutdown(); - // ~LLThread() will be called here -} - -void LLQueuedThread::shutdown() -{ - setQuitting(); - - unpause(); // MAIN THREAD - if (mThreaded) - { - if (mRequestQueue.size() == 0) - { - mRequestQueue.close(); - } - - S32 timeout = 100; - for ( ; timeout>0; timeout--) - { - if (isStopped()) - { - break; - } - ms_sleep(100); - LLThread::yield(); - } - if (timeout == 0) - { - LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL; - } - } - else - { - mStatus = STOPPED; - } - - QueuedRequest* req; - S32 active_count = 0; - while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) - { - if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS) - { - ++active_count; - req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest - } - req->deleteRequest(); - } - if (active_count) - { - LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL; - } - - mRequestQueue.close(); -} - -//---------------------------------------------------------------------------- - -// MAIN THREAD -// virtual -size_t LLQueuedThread::update(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED; - if (!mStarted) - { - if (!mThreaded) - { - startThread(); - mStarted = true; - } - } - return updateQueue(max_time_ms); -} - -size_t LLQueuedThread::updateQueue(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED; - // Frame Update - if (mThreaded) - { - // schedule a call to threadedUpdate for every call to updateQueue - if (!isQuitting()) - { - mRequestQueue.post([=]() - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); - mIdleThread = false; - threadedUpdate(); - mIdleThread = true; - } - ); - } - - if(getPending() > 0) - { - unpause(); - } - } - else - { - mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f))); - threadedUpdate(); - } - return getPending(); -} - -void LLQueuedThread::incQueue() -{ - // Something has been added to the queue - if (!isPaused()) - { - if (mThreaded) - { - wake(); // Wake the thread up if necessary. - } - } -} - -//virtual -// May be called from any thread -size_t LLQueuedThread::getPending() -{ - return mRequestQueue.size(); -} - -// MAIN thread -void LLQueuedThread::waitOnPending() -{ - while(1) - { - update(0); - - if (mIdleThread) - { - break; - } - if (mThreaded) - { - yield(); - } - } - return; -} - -// MAIN thread -void LLQueuedThread::printQueueStats() -{ - U32 size = mRequestQueue.size(); - if (size > 0) - { - LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; - } - else - { - LL_INFOS() << "Queued Thread Idle" << LL_ENDL; - } -} - -// MAIN thread -LLQueuedThread::handle_t LLQueuedThread::generateHandle() -{ - U32 res = ++mNextHandle; - return res; -} - -// MAIN thread -bool LLQueuedThread::addRequest(QueuedRequest* req) -{ - LL_PROFILE_ZONE_SCOPED; - if (mStatus == QUITTING) - { - return false; - } - - lockData(); - req->setStatus(STATUS_QUEUED); - mRequestHash.insert(req); -#if _DEBUG -// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL; -#endif - unlockData(); - - llassert(!mDataLock->isSelfLocked()); - mRequestQueue.post([this, req]() { processRequest(req); }); - - return true; -} - -// MAIN thread -bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) -{ - LL_PROFILE_ZONE_SCOPED; - llassert (handle != nullHandle()); - bool res = false; - bool waspaused = isPaused(); - bool done = false; - while(!done) - { - update(0); // unpauses - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (!req) - { - done = true; // request does not exist - } - else if (req->getStatus() == STATUS_COMPLETE) - { - res = true; - if (auto_complete) - { - mRequestHash.erase(handle); - req->deleteRequest(); -// check(); - } - done = true; - } - unlockData(); - - if (!done && mThreaded) - { - yield(); - } - } - if (waspaused) - { - pause(); - } - return res; -} - -// MAIN thread -LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle) -{ - if (handle == nullHandle()) - { - return 0; - } - lockData(); - QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle); - unlockData(); - return res; -} - -LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) -{ - status_t res = STATUS_EXPIRED; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - res = req->getStatus(); - } - unlockData(); - return res; -} - -void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0)); - } - unlockData(); -} - -// MAIN thread -void LLQueuedThread::setFlags(handle_t handle, U32 flags) -{ - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - req->setFlags(flags); - } - unlockData(); -} - -bool LLQueuedThread::completeRequest(handle_t handle) -{ - LL_PROFILE_ZONE_SCOPED; - bool res = false; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - llassert_always(req->getStatus() != STATUS_QUEUED); - llassert_always(req->getStatus() != STATUS_INPROGRESS); -#if _DEBUG -// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL; -#endif - mRequestHash.erase(handle); - req->deleteRequest(); -// check(); - res = true; - } - unlockData(); - return res; -} - -bool LLQueuedThread::check() -{ -#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging - for (int i=0; i* entry = mRequestHash.get_element_at_index(i); - while (entry) - { - if (entry->getHashKey() > mNextHandle) - { - LL_ERRS() << "Hash Error" << LL_ENDL; - return false; - } - entry = entry->getNextEntry(); - } - } -#endif - return true; -} - -//============================================================================ -// Runs on its OWN thread - -void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - - mIdleThread = false; - //threadedUpdate(); - - // Get next request from pool - lockData(); - - if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort"); - req->setStatus(STATUS_ABORTED); - req->finishRequest(false); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); -// check(); - } - unlockData(); - } - else - { - llassert_always(req->getStatus() == STATUS_QUEUED); - - if (req) - { - req->setStatus(STATUS_INPROGRESS); - } - unlockData(); - - // This is the only place we will call req->setStatus() after - // it has initially been seet to STATUS_QUEUED, so it is - // safe to access req. - if (req) - { - // process request - bool complete = req->processRequest(); - - if (complete) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete"); - lockData(); - req->setStatus(STATUS_COMPLETE); - req->finishRequest(true); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); - // check(); - } - unlockData(); - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry"); - //put back on queue and try again in 0.1ms - lockData(); - req->setStatus(STATUS_QUEUED); - - unlockData(); - - llassert(!mDataLock->isSelfLocked()); - -#if 0 - // try again on next frame - // NOTE: tried using "post" with a time in the future, but this - // would invariably cause this thread to wait for a long time (10+ ms) - // while work is pending - bool ret = LL::WorkQueue::postMaybe( - mMainQueue, - [=]() - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); - mRequestQueue.post([=]() - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues - processRequest(req); - }); - }); - llassert(ret); -#else - using namespace std::chrono_literals; - auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms; - mRequestQueue.post([=] - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); - if (LL::WorkQueue::TimePoint::clock::now() < retry_time) - { - auto sleep_time = std::chrono::duration_cast(retry_time - LL::WorkQueue::TimePoint::clock::now()); - - if (sleep_time.count() > 0) - { - ms_sleep(sleep_time.count()); - } - } - processRequest(req); - }); -#endif - - } - } - } - - mIdleThread = true; -} - -// virtual -bool LLQueuedThread::runCondition() -{ - // mRunCondition must be locked here - if (mRequestQueue.size() == 0 && mIdleThread) - return false; - else - return true; -} - -// virtual -void LLQueuedThread::run() -{ - // call checPause() immediately so we don't try to do anything before the class is fully constructed - checkPause(); - startThread(); - mStarted = true; - - - /*while (1) - { - LL_PROFILE_ZONE_SCOPED; - // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. - checkPause(); - - mIdleThread = false; - - threadedUpdate(); - - auto pending_work = processNextRequest(); - - if (pending_work == 0) - { - //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); - mIdleThread = true; - //ms_sleep(1); - } - //LLThread::yield(); // thread should yield after each request - }*/ - mRequestQueue.runUntilClose(); - - endThread(); - LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL; - - -} - -// virtual -void LLQueuedThread::startThread() -{ -} - -// virtual -void LLQueuedThread::endThread() -{ -} - -// virtual -void LLQueuedThread::threadedUpdate() -{ -} - -//============================================================================ - -LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) : - LLSimpleHashEntry(handle), - mStatus(STATUS_UNKNOWN), - mFlags(flags) -{ -} - -LLQueuedThread::QueuedRequest::~QueuedRequest() -{ - llassert_always(mStatus == STATUS_DELETE); -} - -//virtual -void LLQueuedThread::QueuedRequest::finishRequest(bool completed) -{ -} - -//virtual -void LLQueuedThread::QueuedRequest::deleteRequest() -{ - llassert_always(mStatus != STATUS_INPROGRESS); - setStatus(STATUS_DELETE); - delete this; -} +/** + * @file llqueuedthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llqueuedthread.h" + +#include + +#include "llstl.h" +#include "lltimer.h" // ms_sleep() +#include "llmutex.h" + +//============================================================================ + +// MAIN THREAD +LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : + LLThread(name), + mIdleThread(true), + mNextHandle(0), + mStarted(false), + mThreaded(threaded), + mRequestQueue(name, 1024 * 1024) +{ + llassert(threaded); // not threaded implementation is deprecated + mMainQueue = LL::WorkQueue::getInstance("mainloop"); + + if (mThreaded) + { + if(should_pause) + { + pause() ; //call this before start the thread. + } + + start(); + } +} + +// MAIN THREAD +LLQueuedThread::~LLQueuedThread() +{ + if (!mThreaded) + { + endThread(); + } + shutdown(); + // ~LLThread() will be called here +} + +void LLQueuedThread::shutdown() +{ + setQuitting(); + + unpause(); // MAIN THREAD + if (mThreaded) + { + if (mRequestQueue.size() == 0) + { + mRequestQueue.close(); + } + + S32 timeout = 100; + for ( ; timeout>0; timeout--) + { + if (isStopped()) + { + break; + } + ms_sleep(100); + LLThread::yield(); + } + if (timeout == 0) + { + LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL; + } + } + else + { + mStatus = STOPPED; + } + + QueuedRequest* req; + S32 active_count = 0; + while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) + { + if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS) + { + ++active_count; + req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest + } + req->deleteRequest(); + } + if (active_count) + { + LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL; + } + + mRequestQueue.close(); +} + +//---------------------------------------------------------------------------- + +// MAIN THREAD +// virtual +size_t LLQueuedThread::update(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED; + if (!mStarted) + { + if (!mThreaded) + { + startThread(); + mStarted = true; + } + } + return updateQueue(max_time_ms); +} + +size_t LLQueuedThread::updateQueue(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED; + // Frame Update + if (mThreaded) + { + // schedule a call to threadedUpdate for every call to updateQueue + if (!isQuitting()) + { + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); + mIdleThread = false; + threadedUpdate(); + mIdleThread = true; + } + ); + } + + if(getPending() > 0) + { + unpause(); + } + } + else + { + mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f))); + threadedUpdate(); + } + return getPending(); +} + +void LLQueuedThread::incQueue() +{ + // Something has been added to the queue + if (!isPaused()) + { + if (mThreaded) + { + wake(); // Wake the thread up if necessary. + } + } +} + +//virtual +// May be called from any thread +size_t LLQueuedThread::getPending() +{ + return mRequestQueue.size(); +} + +// MAIN thread +void LLQueuedThread::waitOnPending() +{ + while(1) + { + update(0); + + if (mIdleThread) + { + break; + } + if (mThreaded) + { + yield(); + } + } + return; +} + +// MAIN thread +void LLQueuedThread::printQueueStats() +{ + U32 size = mRequestQueue.size(); + if (size > 0) + { + LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; + } + else + { + LL_INFOS() << "Queued Thread Idle" << LL_ENDL; + } +} + +// MAIN thread +LLQueuedThread::handle_t LLQueuedThread::generateHandle() +{ + U32 res = ++mNextHandle; + return res; +} + +// MAIN thread +bool LLQueuedThread::addRequest(QueuedRequest* req) +{ + LL_PROFILE_ZONE_SCOPED; + if (mStatus == QUITTING) + { + return false; + } + + lockData(); + req->setStatus(STATUS_QUEUED); + mRequestHash.insert(req); +#if _DEBUG +// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL; +#endif + unlockData(); + + llassert(!mDataLock->isSelfLocked()); + mRequestQueue.post([this, req]() { processRequest(req); }); + + return true; +} + +// MAIN thread +bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) +{ + LL_PROFILE_ZONE_SCOPED; + llassert (handle != nullHandle()); + bool res = false; + bool waspaused = isPaused(); + bool done = false; + while(!done) + { + update(0); // unpauses + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (!req) + { + done = true; // request does not exist + } + else if (req->getStatus() == STATUS_COMPLETE) + { + res = true; + if (auto_complete) + { + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + } + done = true; + } + unlockData(); + + if (!done && mThreaded) + { + yield(); + } + } + if (waspaused) + { + pause(); + } + return res; +} + +// MAIN thread +LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle) +{ + if (handle == nullHandle()) + { + return 0; + } + lockData(); + QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle); + unlockData(); + return res; +} + +LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) +{ + status_t res = STATUS_EXPIRED; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + res = req->getStatus(); + } + unlockData(); + return res; +} + +void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0)); + } + unlockData(); +} + +// MAIN thread +void LLQueuedThread::setFlags(handle_t handle, U32 flags) +{ + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + req->setFlags(flags); + } + unlockData(); +} + +bool LLQueuedThread::completeRequest(handle_t handle) +{ + LL_PROFILE_ZONE_SCOPED; + bool res = false; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + llassert_always(req->getStatus() != STATUS_QUEUED); + llassert_always(req->getStatus() != STATUS_INPROGRESS); +#if _DEBUG +// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL; +#endif + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + res = true; + } + unlockData(); + return res; +} + +bool LLQueuedThread::check() +{ +#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging + for (int i=0; i* entry = mRequestHash.get_element_at_index(i); + while (entry) + { + if (entry->getHashKey() > mNextHandle) + { + LL_ERRS() << "Hash Error" << LL_ENDL; + return false; + } + entry = entry->getNextEntry(); + } + } +#endif + return true; +} + +//============================================================================ +// Runs on its OWN thread + +void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + + mIdleThread = false; + //threadedUpdate(); + + // Get next request from pool + lockData(); + + if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort"); + req->setStatus(STATUS_ABORTED); + req->finishRequest(false); + if (req->getFlags() & FLAG_AUTO_COMPLETE) + { + mRequestHash.erase(req); + req->deleteRequest(); +// check(); + } + unlockData(); + } + else + { + llassert_always(req->getStatus() == STATUS_QUEUED); + + if (req) + { + req->setStatus(STATUS_INPROGRESS); + } + unlockData(); + + // This is the only place we will call req->setStatus() after + // it has initially been seet to STATUS_QUEUED, so it is + // safe to access req. + if (req) + { + // process request + bool complete = req->processRequest(); + + if (complete) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete"); + lockData(); + req->setStatus(STATUS_COMPLETE); + req->finishRequest(true); + if (req->getFlags() & FLAG_AUTO_COMPLETE) + { + mRequestHash.erase(req); + req->deleteRequest(); + // check(); + } + unlockData(); + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry"); + //put back on queue and try again in 0.1ms + lockData(); + req->setStatus(STATUS_QUEUED); + + unlockData(); + + llassert(!mDataLock->isSelfLocked()); + +#if 0 + // try again on next frame + // NOTE: tried using "post" with a time in the future, but this + // would invariably cause this thread to wait for a long time (10+ ms) + // while work is pending + bool ret = LL::WorkQueue::postMaybe( + mMainQueue, + [=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues + processRequest(req); + }); + }); + llassert(ret); +#else + using namespace std::chrono_literals; + auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms; + mRequestQueue.post([=] + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + if (LL::WorkQueue::TimePoint::clock::now() < retry_time) + { + auto sleep_time = std::chrono::duration_cast(retry_time - LL::WorkQueue::TimePoint::clock::now()); + + if (sleep_time.count() > 0) + { + ms_sleep(sleep_time.count()); + } + } + processRequest(req); + }); +#endif + + } + } + } + + mIdleThread = true; +} + +// virtual +bool LLQueuedThread::runCondition() +{ + // mRunCondition must be locked here + if (mRequestQueue.size() == 0 && mIdleThread) + return false; + else + return true; +} + +// virtual +void LLQueuedThread::run() +{ + // call checPause() immediately so we don't try to do anything before the class is fully constructed + checkPause(); + startThread(); + mStarted = true; + + + /*while (1) + { + LL_PROFILE_ZONE_SCOPED; + // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. + checkPause(); + + mIdleThread = false; + + threadedUpdate(); + + auto pending_work = processNextRequest(); + + if (pending_work == 0) + { + //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); + mIdleThread = true; + //ms_sleep(1); + } + //LLThread::yield(); // thread should yield after each request + }*/ + mRequestQueue.runUntilClose(); + + endThread(); + LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL; + + +} + +// virtual +void LLQueuedThread::startThread() +{ +} + +// virtual +void LLQueuedThread::endThread() +{ +} + +// virtual +void LLQueuedThread::threadedUpdate() +{ +} + +//============================================================================ + +LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) : + LLSimpleHashEntry(handle), + mStatus(STATUS_UNKNOWN), + mFlags(flags) +{ +} + +LLQueuedThread::QueuedRequest::~QueuedRequest() +{ + llassert_always(mStatus == STATUS_DELETE); +} + +//virtual +void LLQueuedThread::QueuedRequest::finishRequest(bool completed) +{ +} + +//virtual +void LLQueuedThread::QueuedRequest::deleteRequest() +{ + llassert_always(mStatus != STATUS_INPROGRESS); + setStatus(STATUS_DELETE); + delete this; +} diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 0f69f7640f..02d3a96fcc 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -1,178 +1,178 @@ -/** - * @file llqueuedthread.h - * @brief - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLQUEUEDTHREAD_H -#define LL_LLQUEUEDTHREAD_H - -#include -#include -#include -#include - -#include "llatomic.h" - -#include "llthread.h" -#include "llsimplehash.h" -#include "workqueue.h" - -//============================================================================ -// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small -// It is assumed that LLQueuedThreads are rarely created/destroyed. - -class LL_COMMON_API LLQueuedThread : public LLThread -{ - //------------------------------------------------------------------------ -public: - enum status_t { - STATUS_EXPIRED = -1, - STATUS_UNKNOWN = 0, - STATUS_QUEUED = 1, - STATUS_INPROGRESS = 2, - STATUS_COMPLETE = 3, - STATUS_ABORTED = 4, - STATUS_DELETE = 5 - }; - enum flags_t { - FLAG_AUTO_COMPLETE = 1, - FLAG_AUTO_DELETE = 2, // child-class dependent - FLAG_ABORT = 4 - }; - - typedef U32 handle_t; - - //------------------------------------------------------------------------ -public: - - class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry - { - friend class LLQueuedThread; - - protected: - virtual ~QueuedRequest(); // use deleteRequest() - - public: - QueuedRequest(handle_t handle, U32 flags = 0); - - status_t getStatus() - { - return mStatus; - } - U32 getFlags() const - { - return mFlags; - } - - protected: - status_t setStatus(status_t newstatus) - { - status_t oldstatus = mStatus; - mStatus = newstatus; - return oldstatus; - } - void setFlags(U32 flags) - { - // NOTE: flags are |'d - mFlags |= flags; - } - - virtual bool processRequest() = 0; // Return true when request has completed - virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted - virtual void deleteRequest(); // Only method to delete a request - - protected: - LLAtomicBase mStatus; - U32 mFlags; - }; - - //------------------------------------------------------------------------ - -public: - static handle_t nullHandle() { return handle_t(0); } - -public: - LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false); - virtual ~LLQueuedThread(); - virtual void shutdown(); - -private: - // No copy constructor or copy assignment - LLQueuedThread(const LLQueuedThread&); - LLQueuedThread& operator=(const LLQueuedThread&); - - virtual bool runCondition(void); - virtual void run(void); - virtual void startThread(void); - virtual void endThread(void); - virtual void threadedUpdate(void); - -protected: - handle_t generateHandle(); - bool addRequest(QueuedRequest* req); - void processRequest(QueuedRequest* req); - void incQueue(); - -public: - bool waitForResult(handle_t handle, bool auto_complete = true); - - virtual size_t update(F32 max_time_ms); - size_t updateQueue(F32 max_time_ms); - - void waitOnPending(); - void printQueueStats(); - - virtual size_t getPending(); - bool getThreaded() { return mThreaded; } - - // Request accessors - status_t getRequestStatus(handle_t handle); - void abortRequest(handle_t handle, bool autocomplete); - void setFlags(handle_t handle, U32 flags); - bool completeRequest(handle_t handle); - // This is public for support classes like LLWorkerThread, - // but generally the methods above should be used. - QueuedRequest* getRequest(handle_t handle); - - // debug (see source) - bool check(); - -protected: - bool mThreaded; // if false, run on main thread and do updates during update() - bool mStarted; // required when mThreaded is false to call startThread() from update() - LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle - - //typedef std::set request_queue_t; - //request_queue_t mRequestQueue; - LL::WorkQueue mRequestQueue; - LL::WorkQueue::weak_t mMainQueue; - - enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 - typedef LLSimpleHash request_hash_t; - request_hash_t mRequestHash; - - handle_t mNextHandle; -}; - -#endif // LL_LLQUEUEDTHREAD_H +/** + * @file llqueuedthread.h + * @brief + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLQUEUEDTHREAD_H +#define LL_LLQUEUEDTHREAD_H + +#include +#include +#include +#include + +#include "llatomic.h" + +#include "llthread.h" +#include "llsimplehash.h" +#include "workqueue.h" + +//============================================================================ +// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small +// It is assumed that LLQueuedThreads are rarely created/destroyed. + +class LL_COMMON_API LLQueuedThread : public LLThread +{ + //------------------------------------------------------------------------ +public: + enum status_t { + STATUS_EXPIRED = -1, + STATUS_UNKNOWN = 0, + STATUS_QUEUED = 1, + STATUS_INPROGRESS = 2, + STATUS_COMPLETE = 3, + STATUS_ABORTED = 4, + STATUS_DELETE = 5 + }; + enum flags_t { + FLAG_AUTO_COMPLETE = 1, + FLAG_AUTO_DELETE = 2, // child-class dependent + FLAG_ABORT = 4 + }; + + typedef U32 handle_t; + + //------------------------------------------------------------------------ +public: + + class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry + { + friend class LLQueuedThread; + + protected: + virtual ~QueuedRequest(); // use deleteRequest() + + public: + QueuedRequest(handle_t handle, U32 flags = 0); + + status_t getStatus() + { + return mStatus; + } + U32 getFlags() const + { + return mFlags; + } + + protected: + status_t setStatus(status_t newstatus) + { + status_t oldstatus = mStatus; + mStatus = newstatus; + return oldstatus; + } + void setFlags(U32 flags) + { + // NOTE: flags are |'d + mFlags |= flags; + } + + virtual bool processRequest() = 0; // Return true when request has completed + virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted + virtual void deleteRequest(); // Only method to delete a request + + protected: + LLAtomicBase mStatus; + U32 mFlags; + }; + + //------------------------------------------------------------------------ + +public: + static handle_t nullHandle() { return handle_t(0); } + +public: + LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false); + virtual ~LLQueuedThread(); + virtual void shutdown(); + +private: + // No copy constructor or copy assignment + LLQueuedThread(const LLQueuedThread&); + LLQueuedThread& operator=(const LLQueuedThread&); + + virtual bool runCondition(void); + virtual void run(void); + virtual void startThread(void); + virtual void endThread(void); + virtual void threadedUpdate(void); + +protected: + handle_t generateHandle(); + bool addRequest(QueuedRequest* req); + void processRequest(QueuedRequest* req); + void incQueue(); + +public: + bool waitForResult(handle_t handle, bool auto_complete = true); + + virtual size_t update(F32 max_time_ms); + size_t updateQueue(F32 max_time_ms); + + void waitOnPending(); + void printQueueStats(); + + virtual size_t getPending(); + bool getThreaded() { return mThreaded; } + + // Request accessors + status_t getRequestStatus(handle_t handle); + void abortRequest(handle_t handle, bool autocomplete); + void setFlags(handle_t handle, U32 flags); + bool completeRequest(handle_t handle); + // This is public for support classes like LLWorkerThread, + // but generally the methods above should be used. + QueuedRequest* getRequest(handle_t handle); + + // debug (see source) + bool check(); + +protected: + bool mThreaded; // if false, run on main thread and do updates during update() + bool mStarted; // required when mThreaded is false to call startThread() from update() + LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle + + //typedef std::set request_queue_t; + //request_queue_t mRequestQueue; + LL::WorkQueue mRequestQueue; + LL::WorkQueue::weak_t mMainQueue; + + enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 + typedef LLSimpleHash request_hash_t; + request_hash_t mRequestHash; + + handle_t mNextHandle; +}; + +#endif // LL_LLQUEUEDTHREAD_H diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index 977c8d1935..35335e1213 100644 --- a/indra/llcommon/llregistry.h +++ b/indra/llcommon/llregistry.h @@ -1,353 +1,353 @@ -/** - * @file llregistry.h - * @brief template classes for registering name, value pairs in nested scopes, statically, etc. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLREGISTRY_H -#define LL_LLREGISTRY_H - -#include - -#include "llsingleton.h" -#include "llstl.h" - -template -struct LLRegistryDefaultComparator -{ - bool operator()(const T& lhs, const T& rhs) const - { - using std::less; - return less()(lhs, rhs); - } -}; - -template > -class LLRegistry -{ -public: - typedef LLRegistry registry_t; - typedef const KEY& ref_const_key_t; - typedef const VALUE& ref_const_value_t; - typedef const VALUE* ptr_const_value_t; - typedef VALUE* ptr_value_t; - - class Registrar - { - friend class LLRegistry; - public: - typedef std::map registry_map_t; - - bool add(ref_const_key_t key, ref_const_value_t value) - { - if (!mMap.insert(std::make_pair(key, value)).second) - { - LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL; - return false; - } - return true; - } - - void remove(ref_const_key_t key) - { - mMap.erase(key); - } - - void replace(ref_const_key_t key, ref_const_value_t value) - { - mMap[key] = value; - } - - typename registry_map_t::const_iterator beginItems() const - { - return mMap.begin(); - } - - typename registry_map_t::const_iterator endItems() const - { - return mMap.end(); - } - - protected: - ptr_value_t getValue(ref_const_key_t key) - { - typename registry_map_t::iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - typename registry_map_t::const_iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - // if the registry is used to store pointers, and null values are valid entries - // then use this function to check the existence of an entry - bool exists(ref_const_key_t key) const - { - return mMap.find(key) != mMap.end(); - } - - bool empty() const - { - return mMap.empty(); - } - - protected: - // use currentRegistrar() or defaultRegistrar() - Registrar() {} - ~Registrar() {} - - private: - registry_map_t mMap; - }; - - typedef typename std::list scope_list_t; - typedef typename std::list::iterator scope_list_iterator_t; - typedef typename std::list::const_iterator scope_list_const_iterator_t; - - LLRegistry() - {} - - ~LLRegistry() {} - - ptr_value_t getValue(ref_const_key_t key) - { - for(Registrar* scope : mActiveScopes) - { - ptr_value_t valuep = scope->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - for(const Registrar* scope : mActiveScopes) - { - ptr_const_value_t valuep = scope->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - bool exists(ref_const_key_t key) const - { - for(const Registrar* scope : mActiveScopes) - { - if (scope->exists(key)) return true; - } - - return mDefaultRegistrar.exists(key); - } - - bool empty() const - { - for(const Registrar* scope : mActiveScopes) - { - if (!scope->empty()) return false; - } - - return mDefaultRegistrar.empty(); - } - - - Registrar& defaultRegistrar() - { - return mDefaultRegistrar; - } - - const Registrar& defaultRegistrar() const - { - return mDefaultRegistrar; - } - - - Registrar& currentRegistrar() - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - const Registrar& currentRegistrar() const - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - -protected: - void addScope(Registrar* scope) - { - // newer scopes go up front - mActiveScopes.insert(mActiveScopes.begin(), scope); - } - - void removeScope(Registrar* scope) - { - // O(N) but should be near the beggining and N should be small and this is safer than storing iterators - scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); - if (iter != mActiveScopes.end()) - { - mActiveScopes.erase(iter); - } - } - -private: - scope_list_t mActiveScopes; - Registrar mDefaultRegistrar; -}; - -template > -class LLRegistrySingleton - : public LLRegistry, - public LLSingleton -{ - // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton) - // because the concrete class is actually DERIVED_TYPE, not - // LLRegistrySingleton. So each concrete subclass needs - // LLSINGLETON(whatever) -- not this intermediate base class. -public: - typedef LLRegistry registry_t; - typedef const KEY& ref_const_key_t; - typedef const VALUE& ref_const_value_t; - typedef VALUE* ptr_value_t; - typedef const VALUE* ptr_const_value_t; - typedef LLSingleton singleton_t; - - class ScopedRegistrar : public registry_t::Registrar - { - public: - ScopedRegistrar(bool push_scope = true) - { - if (push_scope) - { - pushScope(); - } - } - - ~ScopedRegistrar() - { - if (singleton_t::instanceExists()) - { - popScope(); - } - } - - void pushScope() - { - singleton_t::instance().addScope(this); - } - - void popScope() - { - singleton_t::instance().removeScope(this); - } - - ptr_value_t getValueFromScope(ref_const_key_t key) - { - return getValue(key); - } - - ptr_const_value_t getValueFromScope(ref_const_key_t key) const - { - return getValue(key); - } - - private: - typename std::list::iterator mListIt; - }; - - class StaticRegistrar : public registry_t::Registrar - { - public: - virtual ~StaticRegistrar() {} - StaticRegistrar(ref_const_key_t key, ref_const_value_t value) - { - if (singleton_t::instance().exists(key)) - { - LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL; - } - singleton_t::instance().mStaticScope->add(key, value); - } - }; - - // convenience functions - typedef typename LLRegistry::Registrar& ref_registrar_t; - static ref_registrar_t currentRegistrar() - { - return singleton_t::instance().registry_t::currentRegistrar(); - } - - static ref_registrar_t defaultRegistrar() - { - return singleton_t::instance().registry_t::defaultRegistrar(); - } - - static ptr_value_t getValue(ref_const_key_t key) - { - return singleton_t::instance().registry_t::getValue(key); - } - -protected: - // DERIVED_TYPE needs to derive from LLRegistrySingleton - LLRegistrySingleton() - : mStaticScope(NULL) - {} - - virtual void initSingleton() - { - mStaticScope = new ScopedRegistrar(); - } - - virtual ~LLRegistrySingleton() - { - delete mStaticScope; - } - -private: - ScopedRegistrar* mStaticScope; -}; - -// helper macro for doing static registration -#define GLUED_TOKEN(x, y) x ## y -#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) -#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); - -#endif +/** + * @file llregistry.h + * @brief template classes for registering name, value pairs in nested scopes, statically, etc. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLREGISTRY_H +#define LL_LLREGISTRY_H + +#include + +#include "llsingleton.h" +#include "llstl.h" + +template +struct LLRegistryDefaultComparator +{ + bool operator()(const T& lhs, const T& rhs) const + { + using std::less; + return less()(lhs, rhs); + } +}; + +template > +class LLRegistry +{ +public: + typedef LLRegistry registry_t; + typedef const KEY& ref_const_key_t; + typedef const VALUE& ref_const_value_t; + typedef const VALUE* ptr_const_value_t; + typedef VALUE* ptr_value_t; + + class Registrar + { + friend class LLRegistry; + public: + typedef std::map registry_map_t; + + bool add(ref_const_key_t key, ref_const_value_t value) + { + if (!mMap.insert(std::make_pair(key, value)).second) + { + LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL; + return false; + } + return true; + } + + void remove(ref_const_key_t key) + { + mMap.erase(key); + } + + void replace(ref_const_key_t key, ref_const_value_t value) + { + mMap[key] = value; + } + + typename registry_map_t::const_iterator beginItems() const + { + return mMap.begin(); + } + + typename registry_map_t::const_iterator endItems() const + { + return mMap.end(); + } + + protected: + ptr_value_t getValue(ref_const_key_t key) + { + typename registry_map_t::iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + typename registry_map_t::const_iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + // if the registry is used to store pointers, and null values are valid entries + // then use this function to check the existence of an entry + bool exists(ref_const_key_t key) const + { + return mMap.find(key) != mMap.end(); + } + + bool empty() const + { + return mMap.empty(); + } + + protected: + // use currentRegistrar() or defaultRegistrar() + Registrar() {} + ~Registrar() {} + + private: + registry_map_t mMap; + }; + + typedef typename std::list scope_list_t; + typedef typename std::list::iterator scope_list_iterator_t; + typedef typename std::list::const_iterator scope_list_const_iterator_t; + + LLRegistry() + {} + + ~LLRegistry() {} + + ptr_value_t getValue(ref_const_key_t key) + { + for(Registrar* scope : mActiveScopes) + { + ptr_value_t valuep = scope->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + for(const Registrar* scope : mActiveScopes) + { + ptr_const_value_t valuep = scope->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + bool exists(ref_const_key_t key) const + { + for(const Registrar* scope : mActiveScopes) + { + if (scope->exists(key)) return true; + } + + return mDefaultRegistrar.exists(key); + } + + bool empty() const + { + for(const Registrar* scope : mActiveScopes) + { + if (!scope->empty()) return false; + } + + return mDefaultRegistrar.empty(); + } + + + Registrar& defaultRegistrar() + { + return mDefaultRegistrar; + } + + const Registrar& defaultRegistrar() const + { + return mDefaultRegistrar; + } + + + Registrar& currentRegistrar() + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + const Registrar& currentRegistrar() const + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + +protected: + void addScope(Registrar* scope) + { + // newer scopes go up front + mActiveScopes.insert(mActiveScopes.begin(), scope); + } + + void removeScope(Registrar* scope) + { + // O(N) but should be near the beggining and N should be small and this is safer than storing iterators + scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); + if (iter != mActiveScopes.end()) + { + mActiveScopes.erase(iter); + } + } + +private: + scope_list_t mActiveScopes; + Registrar mDefaultRegistrar; +}; + +template > +class LLRegistrySingleton + : public LLRegistry, + public LLSingleton +{ + // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton) + // because the concrete class is actually DERIVED_TYPE, not + // LLRegistrySingleton. So each concrete subclass needs + // LLSINGLETON(whatever) -- not this intermediate base class. +public: + typedef LLRegistry registry_t; + typedef const KEY& ref_const_key_t; + typedef const VALUE& ref_const_value_t; + typedef VALUE* ptr_value_t; + typedef const VALUE* ptr_const_value_t; + typedef LLSingleton singleton_t; + + class ScopedRegistrar : public registry_t::Registrar + { + public: + ScopedRegistrar(bool push_scope = true) + { + if (push_scope) + { + pushScope(); + } + } + + ~ScopedRegistrar() + { + if (singleton_t::instanceExists()) + { + popScope(); + } + } + + void pushScope() + { + singleton_t::instance().addScope(this); + } + + void popScope() + { + singleton_t::instance().removeScope(this); + } + + ptr_value_t getValueFromScope(ref_const_key_t key) + { + return getValue(key); + } + + ptr_const_value_t getValueFromScope(ref_const_key_t key) const + { + return getValue(key); + } + + private: + typename std::list::iterator mListIt; + }; + + class StaticRegistrar : public registry_t::Registrar + { + public: + virtual ~StaticRegistrar() {} + StaticRegistrar(ref_const_key_t key, ref_const_value_t value) + { + if (singleton_t::instance().exists(key)) + { + LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL; + } + singleton_t::instance().mStaticScope->add(key, value); + } + }; + + // convenience functions + typedef typename LLRegistry::Registrar& ref_registrar_t; + static ref_registrar_t currentRegistrar() + { + return singleton_t::instance().registry_t::currentRegistrar(); + } + + static ref_registrar_t defaultRegistrar() + { + return singleton_t::instance().registry_t::defaultRegistrar(); + } + + static ptr_value_t getValue(ref_const_key_t key) + { + return singleton_t::instance().registry_t::getValue(key); + } + +protected: + // DERIVED_TYPE needs to derive from LLRegistrySingleton + LLRegistrySingleton() + : mStaticScope(NULL) + {} + + virtual void initSingleton() + { + mStaticScope = new ScopedRegistrar(); + } + + virtual ~LLRegistrySingleton() + { + delete mStaticScope; + } + +private: + ScopedRegistrar* mStaticScope; +}; + +// helper macro for doing static registration +#define GLUED_TOKEN(x, y) x ## y +#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) +#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); + +#endif diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 612f71e8bc..92d9392477 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1,2470 +1,2470 @@ -/** - * @file llsdserialize.cpp - * @author Phoenix - * @date 2006-03-05 - * @brief Implementation of LLSD parsers and formatters - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llsdserialize.h" -#include "llpointer.h" -#include "llstreamtools.h" // for fullread - -#include -#include "apr_base64.h" - -#include -#include - -#ifdef LL_USESYSTEMLIBS -# include -#else -# include "zlib-ng/zlib.h" // for davep's dirty little zip functions -#endif - -#if !LL_WINDOWS -#include // htonl & ntohl -#endif - -#include "lldate.h" -#include "llmemorystream.h" -#include "llsd.h" -#include "llstring.h" -#include "lluri.h" - -// File constants -static const size_t MAX_HDR_LEN = 20; -static const S32 UNZIP_LLSD_MAX_DEPTH = 96; -static const char LEGACY_NON_HEADER[] = ""; -const std::string LLSD_BINARY_HEADER("LLSD/Binary"); -const std::string LLSD_XML_HEADER("LLSD/XML"); -const std::string LLSD_NOTATION_HEADER("llsd/notation"); - -//used to deflate a gzipped asset (currently used for navmeshes) -#define windowBits 15 -#define ENABLE_ZLIB_GZIP 32 - -// If we published this in llsdserialize.h, we could use it in the -// implementation of LLSDOStreamer's operator<<(). -template -void format_using(const LLSD& data, std::ostream& ostr, - LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) -{ - LLPointer f{ new Formatter }; - f->format(data, ostr, options); -} - -template -S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) -{ - LLPointer p{ new Parser }; - return p->parse(istr, data, max_bytes, max_depth); -} - -/** - * LLSDSerialize - */ - -// static -void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, - LLSDFormatter::EFormatterOptions options) -{ - LLPointer f = NULL; - - switch (type) - { - case LLSD_BINARY: - str << "\n"; - f = new LLSDBinaryFormatter; - break; - - case LLSD_XML: - str << "\n"; - f = new LLSDXMLFormatter; - break; - - case LLSD_NOTATION: - str << "\n"; - f = new LLSDNotationFormatter; - break; - - default: - LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL; - } - - if (f.notNull()) - { - f->format(sd, str, options); - } -} - -// static -bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes) -{ - char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */ - bool fail_if_not_legacy = false; - - /* - * Get the first line before anything. Don't read more than max_bytes: - * this get() overload reads no more than (count-1) bytes into the - * specified buffer. In the usual case when max_bytes exceeds - * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2. - */ - llssize max_hdr_read = MAX_HDR_LEN; - if (max_bytes != LLSDSerialize::SIZE_UNLIMITED) - { - max_hdr_read = llmin(max_bytes + 1, max_hdr_read); - } - str.get(hdr_buf, max_hdr_read, '\n'); - auto inbuf = str.gcount(); - - // https://en.cppreference.com/w/cpp/io/basic_istream/get - // When the get() above sees the specified delimiter '\n', it stops there - // without pulling it from the stream. If it turns out that the stream - // does NOT contain a header, and the content includes meaningful '\n', - // it's important to pull that into hdr_buf too. - if (inbuf < max_bytes && str.get(hdr_buf[inbuf])) - { - // got the delimiting '\n' - ++inbuf; - // None of the following requires that hdr_buf contain a final '\0' - // byte. We could store one if needed, since even the incremented - // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need. - } - std::string header{ hdr_buf, static_cast(inbuf) }; - if (str.fail()) - { - str.clear(); - fail_if_not_legacy = true; - } - - if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */ - { // Create a LLSD XML parser, and parse the first chunk read above. - LLSDXMLParser x; - x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read - auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it - // Formally we should probably check (parsed != PARSE_FAILURE && - // parsed > 0), but since PARSE_FAILURE is -1, this suffices. - return (parsed > 0); - } - - if (fail_if_not_legacy) - { - LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL; - return false; - } - - /* - * Remove the newline chars - */ - std::string::size_type lastchar = header.find_last_not_of("\r\n"); - if (lastchar != std::string::npos) - { - // It's important that find_last_not_of() returns size_type, which is - // why lastchar explicitly declares the type above. erase(size_type) - // erases from that offset to the end of the string, whereas - // erase(iterator) erases only a single character. - header.erase(lastchar+1); - } - - // trim off the header syntax - auto start = header.find_first_not_of("(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) - { - return (parse_using(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) - { - return (parse_using(str, sd, max_bytes-inbuf) > 0); - } - else // no header we recognize - { - LLPointer p; - if (inbuf && hdr_buf[0] == '<') - { - // looks like XML - LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL; - p = new LLSDXMLParser; - } - else - { - // assume notation - LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL; - p = new LLSDNotationParser; - } - // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that - // data to whatever remains in 'str'. - LLMemoryStreamBuf already(reinterpret_cast(hdr_buf), inbuf); - cat_streambuf prebuff(&already, str.rdbuf()); - std::istream prepend(&prebuff); -#if 1 - return (p->parse(prepend, sd, max_bytes) > 0); -#else - // debugging the reconstituted 'prepend' stream - // allocate a buffer that we hope is big enough for the whole thing - std::vector wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); - prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); - LLMemoryStream replay(reinterpret_cast(wholemsg.data()), prepend.gcount()); - auto success{ p->parse(replay, sd, prepend.gcount()) > 0 }; - { - LL_DEBUGS() << (success? "parsed: $$" : "failed: '") - << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$" - << LL_ENDL; - } - return success; -#endif - } -} - -/** - * Endian handlers - */ -#if LL_BIG_ENDIAN -U64 ll_htonll(U64 hostlonglong) { return hostlonglong; } -U64 ll_ntohll(U64 netlonglong) { return netlonglong; } -F64 ll_htond(F64 hostlonglong) { return hostlonglong; } -F64 ll_ntohd(F64 netlonglong) { return netlonglong; } -#else -// I read some comments one a indicating that doing an integer add -// here would be faster than a bitwise or. For now, the or has -// programmer clarity, since the intended outcome matches the -// operation. -U64 ll_htonll(U64 hostlonglong) -{ - return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) | - ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32)); -} -U64 ll_ntohll(U64 netlonglong) -{ - return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) | - ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32)); -} -union LLEndianSwapper -{ - F64 d; - U64 i; -}; -F64 ll_htond(F64 hostdouble) -{ - LLEndianSwapper tmp; - tmp.d = hostdouble; - tmp.i = ll_htonll(tmp.i); - return tmp.d; -} -F64 ll_ntohd(F64 netdouble) -{ - LLEndianSwapper tmp; - tmp.d = netdouble; - tmp.i = ll_ntohll(tmp.i); - return tmp.d; -} -#endif - -/** - * Local functions. - */ -/** - * @brief Figure out what kind of string it is (raw or delimited) and handoff. - * - * @param istr The stream to read from. - * @param value [out] The string which was found. - * @param max_bytes The maximum possible length of the string. Passing in - * a negative value will skip this check. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes); - -/** - * @brief Parse a delimited string. - * - * @param istr The stream to read from, with the delimiter already popped. - * @param value [out] The string which was found. - * @param d The delimiter to use. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string_delim(std::istream& istr, std::string& value, char d); - -/** - * @brief Read a raw string off the stream. - * - * @param istr The stream to read from, with the (len) parameter - * leading the stream. - * @param value [out] The string which was found. - * @param d The delimiter to use. - * @param max_bytes The maximum possible length of the string. Passing in - * a negative value will skip this check. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string_raw( - std::istream& istr, - std::string& value, - llssize max_bytes); - -/** - * @brief helper method for dealing with the different notation boolean format. - * - * @param istr The stream to read from with the leading character stripped. - * @param data [out] the result of the parse. - * @param compare The string to compare the boolean against - * @param vale The value to assign to data if the parse succeeds. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_boolean( - std::istream& istr, - LLSD& data, - const std::string& compare, - bool value); - -/** - * @brief Do notation escaping of a string to an ostream. - * - * @param value The string to escape and serialize - * @param str The stream to serialize to. - */ -void serialize_string(const std::string& value, std::ostream& str); - - -/** - * Local constants. - */ -static const std::string NOTATION_TRUE_SERIAL("true"); -static const std::string NOTATION_FALSE_SERIAL("false"); - -static const char BINARY_TRUE_SERIAL = '1'; -static const char BINARY_FALSE_SERIAL = '0'; - - -/** - * LLSDParser - */ -LLSDParser::LLSDParser() - : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false) -{ -} - -// virtual -LLSDParser::~LLSDParser() -{ } - -S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth) -{ - mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes; - mMaxBytesLeft = max_bytes; - return doParse(istr, data, max_depth); -} - - -// Parse using routine to get() lines, faster than parse() -S32 LLSDParser::parseLines(std::istream& istr, LLSD& data) -{ - mCheckLimits = false; - mParseLines = true; - return doParse(istr, data); -} - - -int LLSDParser::get(std::istream& istr) const -{ - if(mCheckLimits) --mMaxBytesLeft; - return istr.get(); -} - -std::istream& LLSDParser::get( - std::istream& istr, - char* s, - std::streamsize n, - char delim) const -{ - istr.get(s, n, delim); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -std::istream& LLSDParser::get( - std::istream& istr, - std::streambuf& sb, - char delim) const -{ - istr.get(sb, delim); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -std::istream& LLSDParser::ignore(std::istream& istr) const -{ - istr.ignore(); - if(mCheckLimits) --mMaxBytesLeft; - return istr; -} - -std::istream& LLSDParser::putback(std::istream& istr, char c) const -{ - istr.putback(c); - if(mCheckLimits) ++mMaxBytesLeft; - return istr; -} - -std::istream& LLSDParser::read( - std::istream& istr, - char* s, - std::streamsize n) const -{ - istr.read(s, n); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -void LLSDParser::account(llssize bytes) const -{ - if(mCheckLimits) mMaxBytesLeft -= bytes; -} - - -/** - * LLSDNotationParser - */ -LLSDNotationParser::LLSDNotationParser() -{ -} - -// virtual -LLSDNotationParser::~LLSDNotationParser() -{ } - -// virtual -S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // map: { string:object, string:object } - // array: [ object, object, object ] - // undef: ! - // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE - // integer: i#### - // real: r#### - // uuid: u#### - // string: "g'day" | 'have a "nice" day' | s(size)"raw data" - // uri: l"escaped" - // date: d"YYYY-MM-DDTHH:MM:SS.FFZ" - // binary: b##"ff3120ab1" | b(size)"raw data" - char c; - c = istr.peek(); - if (max_depth == 0) - { - return PARSE_FAILURE; - } - while(isspace(c)) - { - // pop the whitespace. - c = get(istr); - c = istr.peek(); - continue; - } - if(!istr.good()) - { - return 0; - } - S32 parse_count = 1; - switch(c) - { - case '{': - { - S32 child_count = parseMap(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '[': - { - S32 child_count = parseArray(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '!': - c = get(istr); - data.clear(); - break; - - case '0': - c = get(istr); - data = false; - break; - - case 'F': - case 'f': - ignore(istr); - c = istr.peek(); - if(isalpha(c)) - { - auto cnt = deserialize_boolean( - istr, - data, - NOTATION_FALSE_SERIAL, - false); - if(PARSE_FAILURE == cnt) parse_count = cnt; - else account(cnt); - } - else - { - data = false; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case '1': - c = get(istr); - data = true; - break; - - case 'T': - case 't': - ignore(istr); - c = istr.peek(); - if(isalpha(c)) - { - auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true); - if(PARSE_FAILURE == cnt) parse_count = cnt; - else account(cnt); - } - else - { - data = true; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case 'i': - { - c = get(istr); - S32 integer = 0; - istr >> integer; - data = integer; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'r': - { - c = get(istr); - F64 real = 0.0; - istr >> real; - data = real; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'u': - { - c = get(istr); - LLUUID id; - istr >> id; - data = id; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '\"': - case '\'': - case 's': - if(!parseString(istr, data)) - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case 'l': - { - c = get(istr); // pop the 'l' - c = get(istr); // pop the delimiter - std::string str; - auto cnt = deserialize_string_delim(istr, str, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = LLURI(str); - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'd': - { - c = get(istr); // pop the 'd' - c = get(istr); // pop the delimiter - std::string str; - auto cnt = deserialize_string_delim(istr, str, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = LLDate(str); - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'b': - if(!parseBinary(istr, data)) - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - default: - parse_count = PARSE_FAILURE; - LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) - << ")" << LL_ENDL; - break; - } - if(PARSE_FAILURE == parse_count) - { - data.clear(); - } - return parse_count; -} - -S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // map: { string:object, string:object } - map = LLSD::emptyMap(); - S32 parse_count = 0; - char c = get(istr); - if(c == '{') - { - // eat commas, white - bool found_name = false; - std::string name; - c = get(istr); - while(c != '}' && istr.good()) - { - if(!found_name) - { - if((c == '\"') || (c == '\'') || (c == 's')) - { - putback(istr, c); - found_name = true; - auto count = deserialize_string(istr, name, mMaxBytesLeft); - if(PARSE_FAILURE == count) return PARSE_FAILURE; - account(count); - } - c = get(istr); - } - else - { - if(isspace(c) || (c == ':')) - { - c = get(istr); - continue; - } - putback(istr, c); - LLSD child; - S32 count = doParse(istr, child, max_depth); - if(count > 0) - { - // There must be a value for every key, thus - // child_count must be greater than 0. - parse_count += count; - map.insert(name, child); - } - else - { - return PARSE_FAILURE; - } - found_name = false; - c = get(istr); - } - } - if(c != '}') - { - map.clear(); - return PARSE_FAILURE; - } - } - return parse_count; -} - -S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // array: [ object, object, object ] - array = LLSD::emptyArray(); - S32 parse_count = 0; - char c = get(istr); - if(c == '[') - { - // eat commas, white - c = get(istr); - while((c != ']') && istr.good()) - { - LLSD child; - if(isspace(c) || (c == ',')) - { - c = get(istr); - continue; - } - putback(istr, c); - S32 count = doParse(istr, child, max_depth); - if(PARSE_FAILURE == count) - { - return PARSE_FAILURE; - } - else - { - parse_count += count; - array.append(child); - } - c = get(istr); - } - if(c != ']') - { - return PARSE_FAILURE; - } - } - return parse_count; -} - -bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - std::string value; - auto count = deserialize_string(istr, value, mMaxBytesLeft); - if(PARSE_FAILURE == count) return false; - account(count); - data = value; - return true; -} - -bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // binary: b##"ff3120ab1" - // or: b(len)"..." - - // I want to manually control those values here to make sure the - // parser doesn't break when someone changes a constant somewhere - // else. - const U32 BINARY_BUFFER_SIZE = 256; - const U32 STREAM_GET_COUNT = 255; - - // need to read the base out. - char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */ - get(istr, buf, STREAM_GET_COUNT, '"'); - char c = get(istr); - if(c != '"') return false; - if(0 == strncmp("b(", buf, 2)) - { - // We probably have a valid raw binary stream. determine - // the size, and read it. - auto len = strtol(buf + 2, NULL, 0); - if(mCheckLimits && (len > mMaxBytesLeft)) return false; - std::vector value; - if(len) - { - value.resize(len); - account(fullread(istr, (char *)&value[0], len)); - } - c = get(istr); // strip off the trailing double-quote - data = value; - } - else if(0 == strncmp("b64", buf, 3)) - { - // *FIX: A bit inefficient, but works for now. To make the - // format better, I would need to add a hint into the - // serialization format that indicated how long it was. - std::stringstream coded_stream; - get(istr, *(coded_stream.rdbuf()), '\"'); - c = get(istr); - std::string encoded(coded_stream.str()); - S32 len = apr_base64_decode_len(encoded.c_str()); - std::vector value; - if(len) - { - value.resize(len); - len = apr_base64_decode_binary(&value[0], encoded.c_str()); - value.resize(len); - } - data = value; - } - else if(0 == strncmp("b16", buf, 3)) - { - // yay, base 16. We pop the next character which is either a - // double quote or base 16 data. If it's a double quote, we're - // done parsing. If it's not, put the data back, and read the - // stream until the next double quote. - char* read; /*Flawfinder: ignore*/ - U8 byte; - U8 byte_buffer[BINARY_BUFFER_SIZE]; - U8* write; - std::vector value; - c = get(istr); - while(c != '"') - { - putback(istr, c); - read = buf; - write = byte_buffer; - get(istr, buf, STREAM_GET_COUNT, '"'); - c = get(istr); - while(*read != '\0') /*Flawfinder: ignore*/ - { - byte = hex_as_nybble(*read++); - byte = byte << 4; - byte |= hex_as_nybble(*read++); - *write++ = byte; - } - // copy the data out of the byte buffer - value.insert(value.end(), byte_buffer, write); - } - data = value; - } - else - { - return false; - } - return true; -} - - -/** - * LLSDBinaryParser - */ -LLSDBinaryParser::LLSDBinaryParser() -{ -} - -// virtual -LLSDBinaryParser::~LLSDBinaryParser() -{ -} - -// virtual -S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD -/** - * Undefined: '!'
- * Boolean: '1' for true '0' for false
- * Integer: 'i' + 4 bytes network byte order
- * Real: 'r' + 8 bytes IEEE double
- * UUID: 'u' + 16 byte unsigned integer
- * String: 's' + 4 byte integer size + string
- * strings also secretly support the notation format - * Date: 'd' + 8 byte IEEE double for seconds since epoch
- * URI: 'l' + 4 byte integer size + string uri
- * Binary: 'b' + 4 byte integer size + binary data
- * Array: '[' + 4 byte integer size + all values + ']'
- * Map: '{' + 4 byte integer size every(key + value) + '}'
- * map keys are serialized as s + 4 byte integer size + string or in the - * notation format. - */ - char c; - c = get(istr); - if(!istr.good()) - { - return 0; - } - if (max_depth == 0) - { - return PARSE_FAILURE; - } - S32 parse_count = 1; - switch(c) - { - case '{': - { - S32 child_count = parseMap(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '[': - { - S32 child_count = parseArray(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '!': - data.clear(); - break; - - case '0': - data = false; - break; - - case '1': - data = true; - break; - - case 'i': - { - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - data = (S32)ntohl(value_nbo); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL; - } - break; - } - - case 'r': - { - F64 real_nbo = 0.0; - read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/ - data = ll_ntohd(real_nbo); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL; - } - break; - } - - case 'u': - { - LLUUID id; - read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/ - data = id; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL; - } - break; - } - - case '\'': - case '"': - { - std::string value; - auto cnt = deserialize_string_delim(istr, value, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = value; - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string." - << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 's': - { - std::string value; - if(parseString(istr, value)) - { - data = value; - } - else - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'l': - { - std::string value; - if(parseString(istr, value)) - { - data = LLURI(value); - } - else - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'd': - { - F64 real = 0.0; - read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/ - data = LLDate(real); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'b': - { - // We probably have a valid raw binary stream. determine - // the size, and read it. - U32 size_nbo = 0; - read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(size_nbo); - if(mCheckLimits && (size > mMaxBytesLeft)) - { - parse_count = PARSE_FAILURE; - } - else - { - std::vector value; - if(size > 0) - { - value.resize(size); - account(fullread(istr, (char*)&value[0], size)); - } - data = value; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - default: - parse_count = PARSE_FAILURE; - LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) - << ")" << LL_ENDL; - break; - } - if(PARSE_FAILURE == parse_count) - { - data.clear(); - } - return parse_count; -} - -S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const -{ - map = LLSD::emptyMap(); - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - S32 parse_count = 0; - S32 count = 0; - char c = get(istr); - while(c != '}' && (count < size) && istr.good()) - { - std::string name; - switch(c) - { - case 'k': - if(!parseString(istr, name)) - { - return PARSE_FAILURE; - } - break; - case '\'': - case '"': - { - auto cnt = deserialize_string_delim(istr, name, c); - if(PARSE_FAILURE == cnt) return PARSE_FAILURE; - account(cnt); - break; - } - } - LLSD child; - S32 child_count = doParse(istr, child, max_depth); - if(child_count > 0) - { - // There must be a value for every key, thus child_count - // must be greater than 0. - parse_count += child_count; - map.insert(name, child); - } - else - { - return PARSE_FAILURE; - } - ++count; - c = get(istr); - } - if((c != '}') || (count < size)) - { - // Make sure it is correctly terminated and we parsed as many - // as were said to be there. - return PARSE_FAILURE; - } - return parse_count; -} - -S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const -{ - array = LLSD::emptyArray(); - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - - // *FIX: This would be a good place to reserve some space in the - // array... - - S32 parse_count = 0; - S32 count = 0; - char c = istr.peek(); - while((c != ']') && (count < size) && istr.good()) - { - LLSD child; - S32 child_count = doParse(istr, child, max_depth); - if(PARSE_FAILURE == child_count) - { - return PARSE_FAILURE; - } - if(child_count) - { - parse_count += child_count; - array.append(child); - } - ++count; - c = istr.peek(); - } - c = get(istr); - if((c != ']') || (count < size)) - { - // Make sure it is correctly terminated and we parsed as many - // as were said to be there. - return PARSE_FAILURE; - } - return parse_count; -} - -bool LLSDBinaryParser::parseString( - std::istream& istr, - std::string& value) const -{ - // *FIX: This is memory inefficient. - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - if(mCheckLimits && (size > mMaxBytesLeft)) return false; - if(size < 0) return false; - std::vector buf; - if(size) - { - buf.resize(size); - account(fullread(istr, &buf[0], size)); - value.assign(buf.begin(), buf.end()); - } - return true; -} - - -/** - * LLSDFormatter - */ -LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options): - mOptions(options) -{ - boolalpha(boolAlpha); - realFormat(realFmt); -} - -// virtual -LLSDFormatter::~LLSDFormatter() -{ } - -void LLSDFormatter::boolalpha(bool alpha) -{ - mBoolAlpha = alpha; -} - -void LLSDFormatter::realFormat(const std::string& format) -{ - mRealFormat = format; -} - -S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const -{ - // pass options captured by constructor - return format(data, ostr, mOptions); -} - -S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const -{ - return format_impl(data, ostr, options, 0); -} - -void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const -{ - std::string buffer = llformat(mRealFormat.c_str(), real); - ostr << buffer; -} - -/** - * LLSDNotationFormatter - */ -LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat, - EFormatterOptions options): - LLSDFormatter(boolAlpha, realFormat, options) -{ -} - -// virtual -LLSDNotationFormatter::~LLSDNotationFormatter() -{ } - -// static -std::string LLSDNotationFormatter::escapeString(const std::string& in) -{ - std::ostringstream ostr; - serialize_string(in, ostr); - return ostr.str(); -} - -S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, - EFormatterOptions options, U32 level) const -{ - S32 format_count = 1; - std::string pre; - std::string post; - - if (options & LLSDFormatter::OPTIONS_PRETTY) - { - for (U32 i = 0; i < level; i++) - { - pre += " "; - } - post = "\n"; - } - - switch(data.type()) - { - case LLSD::TypeMap: - { - if (0 != level) ostr << post << pre; - ostr << "{"; - std::string inner_pre; - if (options & LLSDFormatter::OPTIONS_PRETTY) - { - inner_pre = pre + " "; - } - - bool need_comma = false; - LLSD::map_const_iterator iter = data.beginMap(); - LLSD::map_const_iterator end = data.endMap(); - for(; iter != end; ++iter) - { - if(need_comma) ostr << ","; - need_comma = true; - ostr << post << inner_pre << '\''; - serialize_string((*iter).first, ostr); - ostr << "':"; - format_count += format_impl((*iter).second, ostr, options, level + 2); - } - ostr << post << pre << "}"; - break; - } - - case LLSD::TypeArray: - { - ostr << post << pre << "["; - bool need_comma = false; - LLSD::array_const_iterator iter = data.beginArray(); - LLSD::array_const_iterator end = data.endArray(); - for(; iter != end; ++iter) - { - if(need_comma) ostr << ","; - need_comma = true; - format_count += format_impl(*iter, ostr, options, level + 1); - } - ostr << "]"; - break; - } - - case LLSD::TypeUndefined: - ostr << "!"; - break; - - case LLSD::TypeBoolean: - if(mBoolAlpha || -#if( LL_WINDOWS || __GNUC__ > 2) - (ostr.flags() & std::ios::boolalpha) -#else - (ostr.flags() & 0x0100) -#endif - ) - { - ostr << (data.asBoolean() - ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL); - } - else - { - ostr << (data.asBoolean() ? 1 : 0); - } - break; - - case LLSD::TypeInteger: - ostr << "i" << data.asInteger(); - break; - - case LLSD::TypeReal: - ostr << "r"; - if(mRealFormat.empty()) - { - ostr << data.asReal(); - } - else - { - formatReal(data.asReal(), ostr); - } - break; - - case LLSD::TypeUUID: - ostr << "u" << data.asUUID(); - break; - - case LLSD::TypeString: - ostr << '\''; - serialize_string(data.asStringRef(), ostr); - ostr << '\''; - break; - - case LLSD::TypeDate: - ostr << "d\"" << data.asDate() << "\""; - break; - - case LLSD::TypeURI: - ostr << "l\""; - serialize_string(data.asString(), ostr); - ostr << "\""; - break; - - case LLSD::TypeBinary: - { - // *FIX: memory inefficient. - const std::vector& buffer = data.asBinary(); - if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY) - { - ostr << "b16\""; - if (! buffer.empty()) - { - std::ios_base::fmtflags old_flags = ostr.flags(); - ostr.setf( std::ios::hex, std::ios::basefield ); - // It shouldn't strictly matter whether the emitted hex digits - // are uppercase; LLSDNotationParser handles either; but as of - // 2020-05-13, Python's llbase.llsd requires uppercase hex. - ostr << std::uppercase; - auto oldfill(ostr.fill('0')); - auto oldwidth(ostr.width()); - for (size_t i = 0; i < buffer.size(); i++) - { - // have to restate setw() before every conversion - ostr << std::setw(2) << (int) buffer[i]; - } - ostr.width(oldwidth); - ostr.fill(oldfill); - ostr.flags(old_flags); - } - } - else // ! OPTIONS_PRETTY_BINARY - { - ostr << "b(" << buffer.size() << ")\""; - if (! buffer.empty()) - { - ostr.write((const char*)&buffer[0], buffer.size()); - } - } - ostr << "\""; - break; - } - - default: - // *NOTE: This should never happen. - ostr << "!"; - break; - } - return format_count; -} - -/** - * LLSDBinaryFormatter - */ -LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat, - EFormatterOptions options): - LLSDFormatter(boolAlpha, realFormat, options) -{ -} - -// virtual -LLSDBinaryFormatter::~LLSDBinaryFormatter() -{ } - -// virtual -S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, - EFormatterOptions options, U32 level) const -{ - S32 format_count = 1; - switch(data.type()) - { - case LLSD::TypeMap: - { - ostr.put('{'); - U32 size_nbo = htonl(data.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - LLSD::map_const_iterator iter = data.beginMap(); - LLSD::map_const_iterator end = data.endMap(); - for(; iter != end; ++iter) - { - ostr.put('k'); - formatString((*iter).first, ostr); - format_count += format_impl((*iter).second, ostr, options, level+1); - } - ostr.put('}'); - break; - } - - case LLSD::TypeArray: - { - ostr.put('['); - U32 size_nbo = htonl(data.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - LLSD::array_const_iterator iter = data.beginArray(); - LLSD::array_const_iterator end = data.endArray(); - for(; iter != end; ++iter) - { - format_count += format_impl(*iter, ostr, options, level+1); - } - ostr.put(']'); - break; - } - - case LLSD::TypeUndefined: - ostr.put('!'); - break; - - case LLSD::TypeBoolean: - if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL); - else ostr.put(BINARY_FALSE_SERIAL); - break; - - case LLSD::TypeInteger: - { - ostr.put('i'); - U32 value_nbo = htonl(data.asInteger()); - ostr.write((const char*)(&value_nbo), sizeof(U32)); - break; - } - - case LLSD::TypeReal: - { - ostr.put('r'); - F64 value_nbo = ll_htond(data.asReal()); - ostr.write((const char*)(&value_nbo), sizeof(F64)); - break; - } - - case LLSD::TypeUUID: - { - ostr.put('u'); - LLUUID temp = data.asUUID(); - ostr.write((const char*)(&(temp.mData)), UUID_BYTES); - break; - } - - case LLSD::TypeString: - ostr.put('s'); - formatString(data.asStringRef(), ostr); - break; - - case LLSD::TypeDate: - { - ostr.put('d'); - F64 value = data.asReal(); - ostr.write((const char*)(&value), sizeof(F64)); - break; - } - - case LLSD::TypeURI: - ostr.put('l'); - formatString(data.asString(), ostr); - break; - - case LLSD::TypeBinary: - { - ostr.put('b'); - const std::vector& buffer = data.asBinary(); - U32 size_nbo = htonl(buffer.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); - break; - } - - default: - // *NOTE: This should never happen. - ostr.put('!'); - break; - } - return format_count; -} - -void LLSDBinaryFormatter::formatString( - const std::string& string, - std::ostream& ostr) const -{ - U32 size_nbo = htonl(string.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - ostr.write(string.c_str(), string.size()); -} - -/** - * local functions - */ -llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes) -{ - int c = istr.get(); - if(istr.fail()) - { - // No data in stream, bail out but mention the character we - // grabbed. - return LLSDParser::PARSE_FAILURE; - } - - llssize rv = LLSDParser::PARSE_FAILURE; - switch(c) - { - case '\'': - case '"': - rv = deserialize_string_delim(istr, value, c); - break; - case 's': - // technically, less than max_bytes, but this is just meant to - // catch egregious protocol errors. parse errors will be - // caught in the case of incorrect counts. - rv = deserialize_string_raw(istr, value, max_bytes); - break; - default: - break; - } - if(LLSDParser::PARSE_FAILURE == rv) return rv; - return rv + 1; // account for the character grabbed at the top. -} - -llssize deserialize_string_delim( - std::istream& istr, - std::string& value, - char delim) -{ - std::ostringstream write_buffer; - bool found_escape = false; - bool found_hex = false; - bool found_digit = false; - U8 byte = 0; - llssize count = 0; - - while (true) - { - int next_byte = istr.get(); - ++count; - - if(istr.fail()) - { - // If our stream is empty, break out - value = write_buffer.str(); - return LLSDParser::PARSE_FAILURE; - } - - char next_char = (char)next_byte; // Now that we know it's not EOF - - if(found_escape) - { - // next character(s) is a special sequence. - if(found_hex) - { - if(found_digit) - { - found_digit = false; - found_hex = false; - found_escape = false; - byte = byte << 4; - byte |= hex_as_nybble(next_char); - write_buffer << byte; - byte = 0; - } - else - { - // next character is the first nybble of - // - found_digit = true; - byte = hex_as_nybble(next_char); - } - } - else if(next_char == 'x') - { - found_hex = true; - } - else - { - switch(next_char) - { - case 'a': - write_buffer << '\a'; - break; - case 'b': - write_buffer << '\b'; - break; - case 'f': - write_buffer << '\f'; - break; - case 'n': - write_buffer << '\n'; - break; - case 'r': - write_buffer << '\r'; - break; - case 't': - write_buffer << '\t'; - break; - case 'v': - write_buffer << '\v'; - break; - default: - write_buffer << next_char; - break; - } - found_escape = false; - } - } - else if(next_char == '\\') - { - found_escape = true; - } - else if(next_char == delim) - { - break; - } - else - { - write_buffer << next_char; - } - } - - value = write_buffer.str(); - return count; -} - -llssize deserialize_string_raw( - std::istream& istr, - std::string& value, - llssize max_bytes) -{ - llssize count = 0; - const S32 BUF_LEN = 20; - char buf[BUF_LEN]; /* Flawfinder: ignore */ - istr.get(buf, BUF_LEN - 1, ')'); - count += istr.gcount(); - int c = istr.get(); - c = istr.get(); - count += 2; - if(((c == '"') || (c == '\'')) && (buf[0] == '(')) - { - // We probably have a valid raw string. determine - // the size, and read it. - // *FIX: This is memory inefficient. - auto len = strtol(buf + 1, NULL, 0); - if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE; - std::vector buf; - if(len) - { - buf.resize(len); - count += fullread(istr, (char *)&buf[0], len); - value.assign(buf.begin(), buf.end()); - } - c = istr.get(); - ++count; - if(!((c == '"') || (c == '\''))) - { - return LLSDParser::PARSE_FAILURE; - } - } - else - { - return LLSDParser::PARSE_FAILURE; - } - return count; -} - -static const char* NOTATION_STRING_CHARACTERS[256] = -{ - "\\x00", // 0 - "\\x01", // 1 - "\\x02", // 2 - "\\x03", // 3 - "\\x04", // 4 - "\\x05", // 5 - "\\x06", // 6 - "\\a", // 7 - "\\b", // 8 - "\\t", // 9 - "\\n", // 10 - "\\v", // 11 - "\\f", // 12 - "\\r", // 13 - "\\x0e", // 14 - "\\x0f", // 15 - "\\x10", // 16 - "\\x11", // 17 - "\\x12", // 18 - "\\x13", // 19 - "\\x14", // 20 - "\\x15", // 21 - "\\x16", // 22 - "\\x17", // 23 - "\\x18", // 24 - "\\x19", // 25 - "\\x1a", // 26 - "\\x1b", // 27 - "\\x1c", // 28 - "\\x1d", // 29 - "\\x1e", // 30 - "\\x1f", // 31 - " ", // 32 - "!", // 33 - "\"", // 34 - "#", // 35 - "$", // 36 - "%", // 37 - "&", // 38 - "\\'", // 39 - "(", // 40 - ")", // 41 - "*", // 42 - "+", // 43 - ",", // 44 - "-", // 45 - ".", // 46 - "/", // 47 - "0", // 48 - "1", // 49 - "2", // 50 - "3", // 51 - "4", // 52 - "5", // 53 - "6", // 54 - "7", // 55 - "8", // 56 - "9", // 57 - ":", // 58 - ";", // 59 - "<", // 60 - "=", // 61 - ">", // 62 - "?", // 63 - "@", // 64 - "A", // 65 - "B", // 66 - "C", // 67 - "D", // 68 - "E", // 69 - "F", // 70 - "G", // 71 - "H", // 72 - "I", // 73 - "J", // 74 - "K", // 75 - "L", // 76 - "M", // 77 - "N", // 78 - "O", // 79 - "P", // 80 - "Q", // 81 - "R", // 82 - "S", // 83 - "T", // 84 - "U", // 85 - "V", // 86 - "W", // 87 - "X", // 88 - "Y", // 89 - "Z", // 90 - "[", // 91 - "\\\\", // 92 - "]", // 93 - "^", // 94 - "_", // 95 - "`", // 96 - "a", // 97 - "b", // 98 - "c", // 99 - "d", // 100 - "e", // 101 - "f", // 102 - "g", // 103 - "h", // 104 - "i", // 105 - "j", // 106 - "k", // 107 - "l", // 108 - "m", // 109 - "n", // 110 - "o", // 111 - "p", // 112 - "q", // 113 - "r", // 114 - "s", // 115 - "t", // 116 - "u", // 117 - "v", // 118 - "w", // 119 - "x", // 120 - "y", // 121 - "z", // 122 - "{", // 123 - "|", // 124 - "}", // 125 - "~", // 126 - "\\x7f", // 127 - "\\x80", // 128 - "\\x81", // 129 - "\\x82", // 130 - "\\x83", // 131 - "\\x84", // 132 - "\\x85", // 133 - "\\x86", // 134 - "\\x87", // 135 - "\\x88", // 136 - "\\x89", // 137 - "\\x8a", // 138 - "\\x8b", // 139 - "\\x8c", // 140 - "\\x8d", // 141 - "\\x8e", // 142 - "\\x8f", // 143 - "\\x90", // 144 - "\\x91", // 145 - "\\x92", // 146 - "\\x93", // 147 - "\\x94", // 148 - "\\x95", // 149 - "\\x96", // 150 - "\\x97", // 151 - "\\x98", // 152 - "\\x99", // 153 - "\\x9a", // 154 - "\\x9b", // 155 - "\\x9c", // 156 - "\\x9d", // 157 - "\\x9e", // 158 - "\\x9f", // 159 - "\\xa0", // 160 - "\\xa1", // 161 - "\\xa2", // 162 - "\\xa3", // 163 - "\\xa4", // 164 - "\\xa5", // 165 - "\\xa6", // 166 - "\\xa7", // 167 - "\\xa8", // 168 - "\\xa9", // 169 - "\\xaa", // 170 - "\\xab", // 171 - "\\xac", // 172 - "\\xad", // 173 - "\\xae", // 174 - "\\xaf", // 175 - "\\xb0", // 176 - "\\xb1", // 177 - "\\xb2", // 178 - "\\xb3", // 179 - "\\xb4", // 180 - "\\xb5", // 181 - "\\xb6", // 182 - "\\xb7", // 183 - "\\xb8", // 184 - "\\xb9", // 185 - "\\xba", // 186 - "\\xbb", // 187 - "\\xbc", // 188 - "\\xbd", // 189 - "\\xbe", // 190 - "\\xbf", // 191 - "\\xc0", // 192 - "\\xc1", // 193 - "\\xc2", // 194 - "\\xc3", // 195 - "\\xc4", // 196 - "\\xc5", // 197 - "\\xc6", // 198 - "\\xc7", // 199 - "\\xc8", // 200 - "\\xc9", // 201 - "\\xca", // 202 - "\\xcb", // 203 - "\\xcc", // 204 - "\\xcd", // 205 - "\\xce", // 206 - "\\xcf", // 207 - "\\xd0", // 208 - "\\xd1", // 209 - "\\xd2", // 210 - "\\xd3", // 211 - "\\xd4", // 212 - "\\xd5", // 213 - "\\xd6", // 214 - "\\xd7", // 215 - "\\xd8", // 216 - "\\xd9", // 217 - "\\xda", // 218 - "\\xdb", // 219 - "\\xdc", // 220 - "\\xdd", // 221 - "\\xde", // 222 - "\\xdf", // 223 - "\\xe0", // 224 - "\\xe1", // 225 - "\\xe2", // 226 - "\\xe3", // 227 - "\\xe4", // 228 - "\\xe5", // 229 - "\\xe6", // 230 - "\\xe7", // 231 - "\\xe8", // 232 - "\\xe9", // 233 - "\\xea", // 234 - "\\xeb", // 235 - "\\xec", // 236 - "\\xed", // 237 - "\\xee", // 238 - "\\xef", // 239 - "\\xf0", // 240 - "\\xf1", // 241 - "\\xf2", // 242 - "\\xf3", // 243 - "\\xf4", // 244 - "\\xf5", // 245 - "\\xf6", // 246 - "\\xf7", // 247 - "\\xf8", // 248 - "\\xf9", // 249 - "\\xfa", // 250 - "\\xfb", // 251 - "\\xfc", // 252 - "\\xfd", // 253 - "\\xfe", // 254 - "\\xff" // 255 -}; - -void serialize_string(const std::string& value, std::ostream& str) -{ - std::string::const_iterator it = value.begin(); - std::string::const_iterator end = value.end(); - U8 c; - for(; it != end; ++it) - { - c = (U8)(*it); - str << NOTATION_STRING_CHARACTERS[c]; - } -} - -llssize deserialize_boolean( - std::istream& istr, - LLSD& data, - const std::string& compare, - bool value) -{ - // - // this method is a little goofy, because it gets the stream at - // the point where the t or f has already been - // consumed. Basically, parse for a patch to the string passed in - // starting at index 1. If it's a match: - // * assign data to value - // * return the number of bytes read - // otherwise: - // * set data to LLSD::null - // * return LLSDParser::PARSE_FAILURE (-1) - // - llssize bytes_read = 0; - std::string::size_type ii = 0; - char c = istr.peek(); - while((++ii < compare.size()) - && (tolower(c) == (int)compare[ii]) - && istr.good()) - { - istr.ignore(); - ++bytes_read; - c = istr.peek(); - } - if(compare.size() != ii) - { - data.clear(); - return LLSDParser::PARSE_FAILURE; - } - data = value; - return bytes_read; -} - -std::ostream& operator<<(std::ostream& s, const LLSD& llsd) -{ - s << LLSDNotationStreamer(llsd); - return s; -} - - -//dirty little zippers -- yell at davep if these are horrid - -//return a string containing gzipped bytes of binary serialized LLSD -// VERY inefficient -- creates several copies of LLSD block in memory -std::string zip_llsd(LLSD& data) -{ - std::stringstream llsd_strm; - - LLSDSerialize::toBinary(data, llsd_strm); - - const U32 CHUNK = 65536; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION); - if (ret != Z_OK) - { - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - - std::string source = llsd_strm.str(); - - U8 out[CHUNK]; - - strm.avail_in = narrow(source.size()); - strm.next_in = (U8*) source.data(); - U8* output = NULL; - - U32 cur_size = 0; - - U32 have = 0; - - do - { - strm.avail_out = CHUNK; - strm.next_out = out; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_OK || ret == Z_STREAM_END) - { //copy result into output - if (strm.avail_out >= CHUNK) - { - deflateEnd(&strm); - if(output) - free(output); - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - - have = CHUNK-strm.avail_out; - U8* new_output = (U8*) realloc(output, cur_size+have); - if (new_output == NULL) - { - LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL; - deflateEnd(&strm); - if (output) - { - free(output); - } - return std::string(); - } - output = new_output; - memcpy(output+cur_size, out, have); - cur_size += have; - } - else - { - deflateEnd(&strm); - if(output) - free(output); - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - } - while (ret == Z_OK); - - std::string::size_type size = cur_size; - - std::string result((char*) output, size); - deflateEnd(&strm); - if(output) - free(output); - - return result; -} - -//decompress a block of LLSD from provided istream -// not very efficient -- creats a copy of decompressed LLSD block in memory -// and deserializes from that copy using LLSDSerialize -LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size) -{ - std::unique_ptr in = std::unique_ptr(new(std::nothrow) U8[size]); - if (!in) - { - return ZR_MEM_ERROR; - } - is.read((char*) in.get(), size); - - return unzip_llsd(data, in.get(), size); -} - -LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size) -{ - U8* result = NULL; - llssize cur_size = 0; - z_stream strm; - - constexpr U32 CHUNK = 1024 * 512; - - static thread_local std::unique_ptr out; - if (!out) - { - out = std::unique_ptr(new(std::nothrow) U8[CHUNK]); - } - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = size; - strm.next_in = const_cast(in); - - S32 ret = inflateInit(&strm); - - do - { - strm.avail_out = CHUNK; - strm.next_out = out.get(); - ret = inflate(&strm, Z_NO_FLUSH); - switch (ret) - { - case Z_NEED_DICT: - case Z_DATA_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_DATA_ERROR; - } - case Z_STREAM_ERROR: - case Z_BUF_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_BUFFER_ERROR; - } - - case Z_MEM_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_MEM_ERROR; - } - } - - U32 have = CHUNK-strm.avail_out; - - U8* new_result = (U8*)realloc(result, cur_size + have); - if (new_result == NULL) - { - inflateEnd(&strm); - if (result) - { - free(result); - } - return ZR_MEM_ERROR; - } - result = new_result; - memcpy(result+cur_size, out.get(), have); - cur_size += have; - - } while (ret == Z_OK && ret != Z_STREAM_END); - - inflateEnd(&strm); - - if (ret != Z_STREAM_END) - { - free(result); - return ZR_DATA_ERROR; - } - - //result now points to the decompressed LLSD block - { - char* result_ptr = strip_deprecated_header((char*)result, cur_size); - - boost::iostreams::stream istrm(result_ptr, cur_size); - - if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH)) - { - free(result); - return ZR_PARSE_ERROR; - } - } - - free(result); - return ZR_OK; -} -//This unzip function will only work with a gzip header and trailer - while the contents -//of the actual compressed data is the same for either format (gzip vs zlib ), the headers -//and trailers are different for the formats. -U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size ) -{ - if (size == 0) - { - LL_WARNS() << "No data to unzip." << LL_ENDL; - return NULL; - } - - U8* result = NULL; - U32 cur_size = 0; - z_stream strm; - - const U32 CHUNK = 0x4000; - - U8 *in = new(std::nothrow) U8[size]; - if (in == NULL) - { - LL_WARNS() << "Memory allocation failure." << LL_ENDL; - return NULL; - } - is.read((char*) in, size); - - U8 out[CHUNK]; - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = size; - strm.next_in = in; - - - S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP ); - do - { - strm.avail_out = CHUNK; - strm.next_out = out; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) - { - inflateEnd(&strm); - free(result); - delete [] in; - valid = false; - } - - switch (ret) - { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&strm); - free(result); - delete [] in; - valid = false; - break; - } - - U32 have = CHUNK-strm.avail_out; - - U8* new_result = (U8*) realloc(result, cur_size + have); - if (new_result == NULL) - { - LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size - << " bytes; requested " << cur_size + have - << " bytes; total syze: ." << size << " bytes." - << LL_ENDL; - inflateEnd(&strm); - if (result) - { - free(result); - } - delete[] in; - valid = false; - return NULL; - } - result = new_result; - memcpy(result+cur_size, out, have); - cur_size += have; - - } while (ret == Z_OK); - - inflateEnd(&strm); - delete [] in; - - if (ret != Z_STREAM_END) - { - free(result); - valid = false; - return NULL; - } - - //result now points to the decompressed LLSD block - { - outsize= cur_size; - valid = true; - } - - return result; -} - -char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size) -{ - const char* deprecated_header = ""; - constexpr size_t deprecated_header_size = 17; - - if (cur_size > deprecated_header_size - && memcmp(in, deprecated_header, deprecated_header_size) == 0) - { - in = in + deprecated_header_size; - cur_size = cur_size - deprecated_header_size; - if (header_size) - { - *header_size = deprecated_header_size + 1; - } - } - - return in; -} - +/** + * @file llsdserialize.cpp + * @author Phoenix + * @date 2006-03-05 + * @brief Implementation of LLSD parsers and formatters + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llsdserialize.h" +#include "llpointer.h" +#include "llstreamtools.h" // for fullread + +#include +#include "apr_base64.h" + +#include +#include + +#ifdef LL_USESYSTEMLIBS +# include +#else +# include "zlib-ng/zlib.h" // for davep's dirty little zip functions +#endif + +#if !LL_WINDOWS +#include // htonl & ntohl +#endif + +#include "lldate.h" +#include "llmemorystream.h" +#include "llsd.h" +#include "llstring.h" +#include "lluri.h" + +// File constants +static const size_t MAX_HDR_LEN = 20; +static const S32 UNZIP_LLSD_MAX_DEPTH = 96; +static const char LEGACY_NON_HEADER[] = ""; +const std::string LLSD_BINARY_HEADER("LLSD/Binary"); +const std::string LLSD_XML_HEADER("LLSD/XML"); +const std::string LLSD_NOTATION_HEADER("llsd/notation"); + +//used to deflate a gzipped asset (currently used for navmeshes) +#define windowBits 15 +#define ENABLE_ZLIB_GZIP 32 + +// If we published this in llsdserialize.h, we could use it in the +// implementation of LLSDOStreamer's operator<<(). +template +void format_using(const LLSD& data, std::ostream& ostr, + LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) +{ + LLPointer f{ new Formatter }; + f->format(data, ostr, options); +} + +template +S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) +{ + LLPointer p{ new Parser }; + return p->parse(istr, data, max_bytes, max_depth); +} + +/** + * LLSDSerialize + */ + +// static +void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, + LLSDFormatter::EFormatterOptions options) +{ + LLPointer f = NULL; + + switch (type) + { + case LLSD_BINARY: + str << "\n"; + f = new LLSDBinaryFormatter; + break; + + case LLSD_XML: + str << "\n"; + f = new LLSDXMLFormatter; + break; + + case LLSD_NOTATION: + str << "\n"; + f = new LLSDNotationFormatter; + break; + + default: + LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL; + } + + if (f.notNull()) + { + f->format(sd, str, options); + } +} + +// static +bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes) +{ + char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */ + bool fail_if_not_legacy = false; + + /* + * Get the first line before anything. Don't read more than max_bytes: + * this get() overload reads no more than (count-1) bytes into the + * specified buffer. In the usual case when max_bytes exceeds + * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2. + */ + llssize max_hdr_read = MAX_HDR_LEN; + if (max_bytes != LLSDSerialize::SIZE_UNLIMITED) + { + max_hdr_read = llmin(max_bytes + 1, max_hdr_read); + } + str.get(hdr_buf, max_hdr_read, '\n'); + auto inbuf = str.gcount(); + + // https://en.cppreference.com/w/cpp/io/basic_istream/get + // When the get() above sees the specified delimiter '\n', it stops there + // without pulling it from the stream. If it turns out that the stream + // does NOT contain a header, and the content includes meaningful '\n', + // it's important to pull that into hdr_buf too. + if (inbuf < max_bytes && str.get(hdr_buf[inbuf])) + { + // got the delimiting '\n' + ++inbuf; + // None of the following requires that hdr_buf contain a final '\0' + // byte. We could store one if needed, since even the incremented + // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need. + } + std::string header{ hdr_buf, static_cast(inbuf) }; + if (str.fail()) + { + str.clear(); + fail_if_not_legacy = true; + } + + if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */ + { // Create a LLSD XML parser, and parse the first chunk read above. + LLSDXMLParser x; + x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read + auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it + // Formally we should probably check (parsed != PARSE_FAILURE && + // parsed > 0), but since PARSE_FAILURE is -1, this suffices. + return (parsed > 0); + } + + if (fail_if_not_legacy) + { + LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL; + return false; + } + + /* + * Remove the newline chars + */ + std::string::size_type lastchar = header.find_last_not_of("\r\n"); + if (lastchar != std::string::npos) + { + // It's important that find_last_not_of() returns size_type, which is + // why lastchar explicitly declares the type above. erase(size_type) + // erases from that offset to the end of the string, whereas + // erase(iterator) erases only a single character. + header.erase(lastchar+1); + } + + // trim off the header syntax + auto start = header.find_first_not_of("(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) + { + return (parse_using(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) + { + return (parse_using(str, sd, max_bytes-inbuf) > 0); + } + else // no header we recognize + { + LLPointer p; + if (inbuf && hdr_buf[0] == '<') + { + // looks like XML + LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL; + p = new LLSDXMLParser; + } + else + { + // assume notation + LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL; + p = new LLSDNotationParser; + } + // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that + // data to whatever remains in 'str'. + LLMemoryStreamBuf already(reinterpret_cast(hdr_buf), inbuf); + cat_streambuf prebuff(&already, str.rdbuf()); + std::istream prepend(&prebuff); +#if 1 + return (p->parse(prepend, sd, max_bytes) > 0); +#else + // debugging the reconstituted 'prepend' stream + // allocate a buffer that we hope is big enough for the whole thing + std::vector wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); + prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); + LLMemoryStream replay(reinterpret_cast(wholemsg.data()), prepend.gcount()); + auto success{ p->parse(replay, sd, prepend.gcount()) > 0 }; + { + LL_DEBUGS() << (success? "parsed: $$" : "failed: '") + << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$" + << LL_ENDL; + } + return success; +#endif + } +} + +/** + * Endian handlers + */ +#if LL_BIG_ENDIAN +U64 ll_htonll(U64 hostlonglong) { return hostlonglong; } +U64 ll_ntohll(U64 netlonglong) { return netlonglong; } +F64 ll_htond(F64 hostlonglong) { return hostlonglong; } +F64 ll_ntohd(F64 netlonglong) { return netlonglong; } +#else +// I read some comments one a indicating that doing an integer add +// here would be faster than a bitwise or. For now, the or has +// programmer clarity, since the intended outcome matches the +// operation. +U64 ll_htonll(U64 hostlonglong) +{ + return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32)); +} +U64 ll_ntohll(U64 netlonglong) +{ + return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32)); +} +union LLEndianSwapper +{ + F64 d; + U64 i; +}; +F64 ll_htond(F64 hostdouble) +{ + LLEndianSwapper tmp; + tmp.d = hostdouble; + tmp.i = ll_htonll(tmp.i); + return tmp.d; +} +F64 ll_ntohd(F64 netdouble) +{ + LLEndianSwapper tmp; + tmp.d = netdouble; + tmp.i = ll_ntohll(tmp.i); + return tmp.d; +} +#endif + +/** + * Local functions. + */ +/** + * @brief Figure out what kind of string it is (raw or delimited) and handoff. + * + * @param istr The stream to read from. + * @param value [out] The string which was found. + * @param max_bytes The maximum possible length of the string. Passing in + * a negative value will skip this check. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes); + +/** + * @brief Parse a delimited string. + * + * @param istr The stream to read from, with the delimiter already popped. + * @param value [out] The string which was found. + * @param d The delimiter to use. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string_delim(std::istream& istr, std::string& value, char d); + +/** + * @brief Read a raw string off the stream. + * + * @param istr The stream to read from, with the (len) parameter + * leading the stream. + * @param value [out] The string which was found. + * @param d The delimiter to use. + * @param max_bytes The maximum possible length of the string. Passing in + * a negative value will skip this check. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string_raw( + std::istream& istr, + std::string& value, + llssize max_bytes); + +/** + * @brief helper method for dealing with the different notation boolean format. + * + * @param istr The stream to read from with the leading character stripped. + * @param data [out] the result of the parse. + * @param compare The string to compare the boolean against + * @param vale The value to assign to data if the parse succeeds. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_boolean( + std::istream& istr, + LLSD& data, + const std::string& compare, + bool value); + +/** + * @brief Do notation escaping of a string to an ostream. + * + * @param value The string to escape and serialize + * @param str The stream to serialize to. + */ +void serialize_string(const std::string& value, std::ostream& str); + + +/** + * Local constants. + */ +static const std::string NOTATION_TRUE_SERIAL("true"); +static const std::string NOTATION_FALSE_SERIAL("false"); + +static const char BINARY_TRUE_SERIAL = '1'; +static const char BINARY_FALSE_SERIAL = '0'; + + +/** + * LLSDParser + */ +LLSDParser::LLSDParser() + : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false) +{ +} + +// virtual +LLSDParser::~LLSDParser() +{ } + +S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth) +{ + mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes; + mMaxBytesLeft = max_bytes; + return doParse(istr, data, max_depth); +} + + +// Parse using routine to get() lines, faster than parse() +S32 LLSDParser::parseLines(std::istream& istr, LLSD& data) +{ + mCheckLimits = false; + mParseLines = true; + return doParse(istr, data); +} + + +int LLSDParser::get(std::istream& istr) const +{ + if(mCheckLimits) --mMaxBytesLeft; + return istr.get(); +} + +std::istream& LLSDParser::get( + std::istream& istr, + char* s, + std::streamsize n, + char delim) const +{ + istr.get(s, n, delim); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +std::istream& LLSDParser::get( + std::istream& istr, + std::streambuf& sb, + char delim) const +{ + istr.get(sb, delim); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +std::istream& LLSDParser::ignore(std::istream& istr) const +{ + istr.ignore(); + if(mCheckLimits) --mMaxBytesLeft; + return istr; +} + +std::istream& LLSDParser::putback(std::istream& istr, char c) const +{ + istr.putback(c); + if(mCheckLimits) ++mMaxBytesLeft; + return istr; +} + +std::istream& LLSDParser::read( + std::istream& istr, + char* s, + std::streamsize n) const +{ + istr.read(s, n); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +void LLSDParser::account(llssize bytes) const +{ + if(mCheckLimits) mMaxBytesLeft -= bytes; +} + + +/** + * LLSDNotationParser + */ +LLSDNotationParser::LLSDNotationParser() +{ +} + +// virtual +LLSDNotationParser::~LLSDNotationParser() +{ } + +// virtual +S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // map: { string:object, string:object } + // array: [ object, object, object ] + // undef: ! + // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE + // integer: i#### + // real: r#### + // uuid: u#### + // string: "g'day" | 'have a "nice" day' | s(size)"raw data" + // uri: l"escaped" + // date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + // binary: b##"ff3120ab1" | b(size)"raw data" + char c; + c = istr.peek(); + if (max_depth == 0) + { + return PARSE_FAILURE; + } + while(isspace(c)) + { + // pop the whitespace. + c = get(istr); + c = istr.peek(); + continue; + } + if(!istr.good()) + { + return 0; + } + S32 parse_count = 1; + switch(c) + { + case '{': + { + S32 child_count = parseMap(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '[': + { + S32 child_count = parseArray(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '!': + c = get(istr); + data.clear(); + break; + + case '0': + c = get(istr); + data = false; + break; + + case 'F': + case 'f': + ignore(istr); + c = istr.peek(); + if(isalpha(c)) + { + auto cnt = deserialize_boolean( + istr, + data, + NOTATION_FALSE_SERIAL, + false); + if(PARSE_FAILURE == cnt) parse_count = cnt; + else account(cnt); + } + else + { + data = false; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case '1': + c = get(istr); + data = true; + break; + + case 'T': + case 't': + ignore(istr); + c = istr.peek(); + if(isalpha(c)) + { + auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true); + if(PARSE_FAILURE == cnt) parse_count = cnt; + else account(cnt); + } + else + { + data = true; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case 'i': + { + c = get(istr); + S32 integer = 0; + istr >> integer; + data = integer; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'r': + { + c = get(istr); + F64 real = 0.0; + istr >> real; + data = real; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'u': + { + c = get(istr); + LLUUID id; + istr >> id; + data = id; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '\"': + case '\'': + case 's': + if(!parseString(istr, data)) + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case 'l': + { + c = get(istr); // pop the 'l' + c = get(istr); // pop the delimiter + std::string str; + auto cnt = deserialize_string_delim(istr, str, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = LLURI(str); + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'd': + { + c = get(istr); // pop the 'd' + c = get(istr); // pop the delimiter + std::string str; + auto cnt = deserialize_string_delim(istr, str, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = LLDate(str); + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'b': + if(!parseBinary(istr, data)) + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + default: + parse_count = PARSE_FAILURE; + LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) + << ")" << LL_ENDL; + break; + } + if(PARSE_FAILURE == parse_count) + { + data.clear(); + } + return parse_count; +} + +S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // map: { string:object, string:object } + map = LLSD::emptyMap(); + S32 parse_count = 0; + char c = get(istr); + if(c == '{') + { + // eat commas, white + bool found_name = false; + std::string name; + c = get(istr); + while(c != '}' && istr.good()) + { + if(!found_name) + { + if((c == '\"') || (c == '\'') || (c == 's')) + { + putback(istr, c); + found_name = true; + auto count = deserialize_string(istr, name, mMaxBytesLeft); + if(PARSE_FAILURE == count) return PARSE_FAILURE; + account(count); + } + c = get(istr); + } + else + { + if(isspace(c) || (c == ':')) + { + c = get(istr); + continue; + } + putback(istr, c); + LLSD child; + S32 count = doParse(istr, child, max_depth); + if(count > 0) + { + // There must be a value for every key, thus + // child_count must be greater than 0. + parse_count += count; + map.insert(name, child); + } + else + { + return PARSE_FAILURE; + } + found_name = false; + c = get(istr); + } + } + if(c != '}') + { + map.clear(); + return PARSE_FAILURE; + } + } + return parse_count; +} + +S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // array: [ object, object, object ] + array = LLSD::emptyArray(); + S32 parse_count = 0; + char c = get(istr); + if(c == '[') + { + // eat commas, white + c = get(istr); + while((c != ']') && istr.good()) + { + LLSD child; + if(isspace(c) || (c == ',')) + { + c = get(istr); + continue; + } + putback(istr, c); + S32 count = doParse(istr, child, max_depth); + if(PARSE_FAILURE == count) + { + return PARSE_FAILURE; + } + else + { + parse_count += count; + array.append(child); + } + c = get(istr); + } + if(c != ']') + { + return PARSE_FAILURE; + } + } + return parse_count; +} + +bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + std::string value; + auto count = deserialize_string(istr, value, mMaxBytesLeft); + if(PARSE_FAILURE == count) return false; + account(count); + data = value; + return true; +} + +bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // binary: b##"ff3120ab1" + // or: b(len)"..." + + // I want to manually control those values here to make sure the + // parser doesn't break when someone changes a constant somewhere + // else. + const U32 BINARY_BUFFER_SIZE = 256; + const U32 STREAM_GET_COUNT = 255; + + // need to read the base out. + char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */ + get(istr, buf, STREAM_GET_COUNT, '"'); + char c = get(istr); + if(c != '"') return false; + if(0 == strncmp("b(", buf, 2)) + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + auto len = strtol(buf + 2, NULL, 0); + if(mCheckLimits && (len > mMaxBytesLeft)) return false; + std::vector value; + if(len) + { + value.resize(len); + account(fullread(istr, (char *)&value[0], len)); + } + c = get(istr); // strip off the trailing double-quote + data = value; + } + else if(0 == strncmp("b64", buf, 3)) + { + // *FIX: A bit inefficient, but works for now. To make the + // format better, I would need to add a hint into the + // serialization format that indicated how long it was. + std::stringstream coded_stream; + get(istr, *(coded_stream.rdbuf()), '\"'); + c = get(istr); + std::string encoded(coded_stream.str()); + S32 len = apr_base64_decode_len(encoded.c_str()); + std::vector value; + if(len) + { + value.resize(len); + len = apr_base64_decode_binary(&value[0], encoded.c_str()); + value.resize(len); + } + data = value; + } + else if(0 == strncmp("b16", buf, 3)) + { + // yay, base 16. We pop the next character which is either a + // double quote or base 16 data. If it's a double quote, we're + // done parsing. If it's not, put the data back, and read the + // stream until the next double quote. + char* read; /*Flawfinder: ignore*/ + U8 byte; + U8 byte_buffer[BINARY_BUFFER_SIZE]; + U8* write; + std::vector value; + c = get(istr); + while(c != '"') + { + putback(istr, c); + read = buf; + write = byte_buffer; + get(istr, buf, STREAM_GET_COUNT, '"'); + c = get(istr); + while(*read != '\0') /*Flawfinder: ignore*/ + { + byte = hex_as_nybble(*read++); + byte = byte << 4; + byte |= hex_as_nybble(*read++); + *write++ = byte; + } + // copy the data out of the byte buffer + value.insert(value.end(), byte_buffer, write); + } + data = value; + } + else + { + return false; + } + return true; +} + + +/** + * LLSDBinaryParser + */ +LLSDBinaryParser::LLSDBinaryParser() +{ +} + +// virtual +LLSDBinaryParser::~LLSDBinaryParser() +{ +} + +// virtual +S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD +/** + * Undefined: '!'
+ * Boolean: '1' for true '0' for false
+ * Integer: 'i' + 4 bytes network byte order
+ * Real: 'r' + 8 bytes IEEE double
+ * UUID: 'u' + 16 byte unsigned integer
+ * String: 's' + 4 byte integer size + string
+ * strings also secretly support the notation format + * Date: 'd' + 8 byte IEEE double for seconds since epoch
+ * URI: 'l' + 4 byte integer size + string uri
+ * Binary: 'b' + 4 byte integer size + binary data
+ * Array: '[' + 4 byte integer size + all values + ']'
+ * Map: '{' + 4 byte integer size every(key + value) + '}'
+ * map keys are serialized as s + 4 byte integer size + string or in the + * notation format. + */ + char c; + c = get(istr); + if(!istr.good()) + { + return 0; + } + if (max_depth == 0) + { + return PARSE_FAILURE; + } + S32 parse_count = 1; + switch(c) + { + case '{': + { + S32 child_count = parseMap(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '[': + { + S32 child_count = parseArray(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '!': + data.clear(); + break; + + case '0': + data = false; + break; + + case '1': + data = true; + break; + + case 'i': + { + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + data = (S32)ntohl(value_nbo); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL; + } + break; + } + + case 'r': + { + F64 real_nbo = 0.0; + read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/ + data = ll_ntohd(real_nbo); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL; + } + break; + } + + case 'u': + { + LLUUID id; + read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/ + data = id; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL; + } + break; + } + + case '\'': + case '"': + { + std::string value; + auto cnt = deserialize_string_delim(istr, value, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = value; + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string." + << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 's': + { + std::string value; + if(parseString(istr, value)) + { + data = value; + } + else + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'l': + { + std::string value; + if(parseString(istr, value)) + { + data = LLURI(value); + } + else + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'd': + { + F64 real = 0.0; + read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/ + data = LLDate(real); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'b': + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + U32 size_nbo = 0; + read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(size_nbo); + if(mCheckLimits && (size > mMaxBytesLeft)) + { + parse_count = PARSE_FAILURE; + } + else + { + std::vector value; + if(size > 0) + { + value.resize(size); + account(fullread(istr, (char*)&value[0], size)); + } + data = value; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + default: + parse_count = PARSE_FAILURE; + LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) + << ")" << LL_ENDL; + break; + } + if(PARSE_FAILURE == parse_count) + { + data.clear(); + } + return parse_count; +} + +S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const +{ + map = LLSD::emptyMap(); + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + S32 parse_count = 0; + S32 count = 0; + char c = get(istr); + while(c != '}' && (count < size) && istr.good()) + { + std::string name; + switch(c) + { + case 'k': + if(!parseString(istr, name)) + { + return PARSE_FAILURE; + } + break; + case '\'': + case '"': + { + auto cnt = deserialize_string_delim(istr, name, c); + if(PARSE_FAILURE == cnt) return PARSE_FAILURE; + account(cnt); + break; + } + } + LLSD child; + S32 child_count = doParse(istr, child, max_depth); + if(child_count > 0) + { + // There must be a value for every key, thus child_count + // must be greater than 0. + parse_count += child_count; + map.insert(name, child); + } + else + { + return PARSE_FAILURE; + } + ++count; + c = get(istr); + } + if((c != '}') || (count < size)) + { + // Make sure it is correctly terminated and we parsed as many + // as were said to be there. + return PARSE_FAILURE; + } + return parse_count; +} + +S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const +{ + array = LLSD::emptyArray(); + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + + // *FIX: This would be a good place to reserve some space in the + // array... + + S32 parse_count = 0; + S32 count = 0; + char c = istr.peek(); + while((c != ']') && (count < size) && istr.good()) + { + LLSD child; + S32 child_count = doParse(istr, child, max_depth); + if(PARSE_FAILURE == child_count) + { + return PARSE_FAILURE; + } + if(child_count) + { + parse_count += child_count; + array.append(child); + } + ++count; + c = istr.peek(); + } + c = get(istr); + if((c != ']') || (count < size)) + { + // Make sure it is correctly terminated and we parsed as many + // as were said to be there. + return PARSE_FAILURE; + } + return parse_count; +} + +bool LLSDBinaryParser::parseString( + std::istream& istr, + std::string& value) const +{ + // *FIX: This is memory inefficient. + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + if(mCheckLimits && (size > mMaxBytesLeft)) return false; + if(size < 0) return false; + std::vector buf; + if(size) + { + buf.resize(size); + account(fullread(istr, &buf[0], size)); + value.assign(buf.begin(), buf.end()); + } + return true; +} + + +/** + * LLSDFormatter + */ +LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options): + mOptions(options) +{ + boolalpha(boolAlpha); + realFormat(realFmt); +} + +// virtual +LLSDFormatter::~LLSDFormatter() +{ } + +void LLSDFormatter::boolalpha(bool alpha) +{ + mBoolAlpha = alpha; +} + +void LLSDFormatter::realFormat(const std::string& format) +{ + mRealFormat = format; +} + +S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const +{ + // pass options captured by constructor + return format(data, ostr, mOptions); +} + +S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const +{ + return format_impl(data, ostr, options, 0); +} + +void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const +{ + std::string buffer = llformat(mRealFormat.c_str(), real); + ostr << buffer; +} + +/** + * LLSDNotationFormatter + */ +LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat, + EFormatterOptions options): + LLSDFormatter(boolAlpha, realFormat, options) +{ +} + +// virtual +LLSDNotationFormatter::~LLSDNotationFormatter() +{ } + +// static +std::string LLSDNotationFormatter::escapeString(const std::string& in) +{ + std::ostringstream ostr; + serialize_string(in, ostr); + return ostr.str(); +} + +S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, + EFormatterOptions options, U32 level) const +{ + S32 format_count = 1; + std::string pre; + std::string post; + + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + for (U32 i = 0; i < level; i++) + { + pre += " "; + } + post = "\n"; + } + + switch(data.type()) + { + case LLSD::TypeMap: + { + if (0 != level) ostr << post << pre; + ostr << "{"; + std::string inner_pre; + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + inner_pre = pre + " "; + } + + bool need_comma = false; + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + ostr << post << inner_pre << '\''; + serialize_string((*iter).first, ostr); + ostr << "':"; + format_count += format_impl((*iter).second, ostr, options, level + 2); + } + ostr << post << pre << "}"; + break; + } + + case LLSD::TypeArray: + { + ostr << post << pre << "["; + bool need_comma = false; + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + format_count += format_impl(*iter, ostr, options, level + 1); + } + ostr << "]"; + break; + } + + case LLSD::TypeUndefined: + ostr << "!"; + break; + + case LLSD::TypeBoolean: + if(mBoolAlpha || +#if( LL_WINDOWS || __GNUC__ > 2) + (ostr.flags() & std::ios::boolalpha) +#else + (ostr.flags() & 0x0100) +#endif + ) + { + ostr << (data.asBoolean() + ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL); + } + else + { + ostr << (data.asBoolean() ? 1 : 0); + } + break; + + case LLSD::TypeInteger: + ostr << "i" << data.asInteger(); + break; + + case LLSD::TypeReal: + ostr << "r"; + if(mRealFormat.empty()) + { + ostr << data.asReal(); + } + else + { + formatReal(data.asReal(), ostr); + } + break; + + case LLSD::TypeUUID: + ostr << "u" << data.asUUID(); + break; + + case LLSD::TypeString: + ostr << '\''; + serialize_string(data.asStringRef(), ostr); + ostr << '\''; + break; + + case LLSD::TypeDate: + ostr << "d\"" << data.asDate() << "\""; + break; + + case LLSD::TypeURI: + ostr << "l\""; + serialize_string(data.asString(), ostr); + ostr << "\""; + break; + + case LLSD::TypeBinary: + { + // *FIX: memory inefficient. + const std::vector& buffer = data.asBinary(); + if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY) + { + ostr << "b16\""; + if (! buffer.empty()) + { + std::ios_base::fmtflags old_flags = ostr.flags(); + ostr.setf( std::ios::hex, std::ios::basefield ); + // It shouldn't strictly matter whether the emitted hex digits + // are uppercase; LLSDNotationParser handles either; but as of + // 2020-05-13, Python's llbase.llsd requires uppercase hex. + ostr << std::uppercase; + auto oldfill(ostr.fill('0')); + auto oldwidth(ostr.width()); + for (size_t i = 0; i < buffer.size(); i++) + { + // have to restate setw() before every conversion + ostr << std::setw(2) << (int) buffer[i]; + } + ostr.width(oldwidth); + ostr.fill(oldfill); + ostr.flags(old_flags); + } + } + else // ! OPTIONS_PRETTY_BINARY + { + ostr << "b(" << buffer.size() << ")\""; + if (! buffer.empty()) + { + ostr.write((const char*)&buffer[0], buffer.size()); + } + } + ostr << "\""; + break; + } + + default: + // *NOTE: This should never happen. + ostr << "!"; + break; + } + return format_count; +} + +/** + * LLSDBinaryFormatter + */ +LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat, + EFormatterOptions options): + LLSDFormatter(boolAlpha, realFormat, options) +{ +} + +// virtual +LLSDBinaryFormatter::~LLSDBinaryFormatter() +{ } + +// virtual +S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, + EFormatterOptions options, U32 level) const +{ + S32 format_count = 1; + switch(data.type()) + { + case LLSD::TypeMap: + { + ostr.put('{'); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + ostr.put('k'); + formatString((*iter).first, ostr); + format_count += format_impl((*iter).second, ostr, options, level+1); + } + ostr.put('}'); + break; + } + + case LLSD::TypeArray: + { + ostr.put('['); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + format_count += format_impl(*iter, ostr, options, level+1); + } + ostr.put(']'); + break; + } + + case LLSD::TypeUndefined: + ostr.put('!'); + break; + + case LLSD::TypeBoolean: + if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL); + else ostr.put(BINARY_FALSE_SERIAL); + break; + + case LLSD::TypeInteger: + { + ostr.put('i'); + U32 value_nbo = htonl(data.asInteger()); + ostr.write((const char*)(&value_nbo), sizeof(U32)); + break; + } + + case LLSD::TypeReal: + { + ostr.put('r'); + F64 value_nbo = ll_htond(data.asReal()); + ostr.write((const char*)(&value_nbo), sizeof(F64)); + break; + } + + case LLSD::TypeUUID: + { + ostr.put('u'); + LLUUID temp = data.asUUID(); + ostr.write((const char*)(&(temp.mData)), UUID_BYTES); + break; + } + + case LLSD::TypeString: + ostr.put('s'); + formatString(data.asStringRef(), ostr); + break; + + case LLSD::TypeDate: + { + ostr.put('d'); + F64 value = data.asReal(); + ostr.write((const char*)(&value), sizeof(F64)); + break; + } + + case LLSD::TypeURI: + ostr.put('l'); + formatString(data.asString(), ostr); + break; + + case LLSD::TypeBinary: + { + ostr.put('b'); + const std::vector& buffer = data.asBinary(); + U32 size_nbo = htonl(buffer.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); + break; + } + + default: + // *NOTE: This should never happen. + ostr.put('!'); + break; + } + return format_count; +} + +void LLSDBinaryFormatter::formatString( + const std::string& string, + std::ostream& ostr) const +{ + U32 size_nbo = htonl(string.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + ostr.write(string.c_str(), string.size()); +} + +/** + * local functions + */ +llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes) +{ + int c = istr.get(); + if(istr.fail()) + { + // No data in stream, bail out but mention the character we + // grabbed. + return LLSDParser::PARSE_FAILURE; + } + + llssize rv = LLSDParser::PARSE_FAILURE; + switch(c) + { + case '\'': + case '"': + rv = deserialize_string_delim(istr, value, c); + break; + case 's': + // technically, less than max_bytes, but this is just meant to + // catch egregious protocol errors. parse errors will be + // caught in the case of incorrect counts. + rv = deserialize_string_raw(istr, value, max_bytes); + break; + default: + break; + } + if(LLSDParser::PARSE_FAILURE == rv) return rv; + return rv + 1; // account for the character grabbed at the top. +} + +llssize deserialize_string_delim( + std::istream& istr, + std::string& value, + char delim) +{ + std::ostringstream write_buffer; + bool found_escape = false; + bool found_hex = false; + bool found_digit = false; + U8 byte = 0; + llssize count = 0; + + while (true) + { + int next_byte = istr.get(); + ++count; + + if(istr.fail()) + { + // If our stream is empty, break out + value = write_buffer.str(); + return LLSDParser::PARSE_FAILURE; + } + + char next_char = (char)next_byte; // Now that we know it's not EOF + + if(found_escape) + { + // next character(s) is a special sequence. + if(found_hex) + { + if(found_digit) + { + found_digit = false; + found_hex = false; + found_escape = false; + byte = byte << 4; + byte |= hex_as_nybble(next_char); + write_buffer << byte; + byte = 0; + } + else + { + // next character is the first nybble of + // + found_digit = true; + byte = hex_as_nybble(next_char); + } + } + else if(next_char == 'x') + { + found_hex = true; + } + else + { + switch(next_char) + { + case 'a': + write_buffer << '\a'; + break; + case 'b': + write_buffer << '\b'; + break; + case 'f': + write_buffer << '\f'; + break; + case 'n': + write_buffer << '\n'; + break; + case 'r': + write_buffer << '\r'; + break; + case 't': + write_buffer << '\t'; + break; + case 'v': + write_buffer << '\v'; + break; + default: + write_buffer << next_char; + break; + } + found_escape = false; + } + } + else if(next_char == '\\') + { + found_escape = true; + } + else if(next_char == delim) + { + break; + } + else + { + write_buffer << next_char; + } + } + + value = write_buffer.str(); + return count; +} + +llssize deserialize_string_raw( + std::istream& istr, + std::string& value, + llssize max_bytes) +{ + llssize count = 0; + const S32 BUF_LEN = 20; + char buf[BUF_LEN]; /* Flawfinder: ignore */ + istr.get(buf, BUF_LEN - 1, ')'); + count += istr.gcount(); + int c = istr.get(); + c = istr.get(); + count += 2; + if(((c == '"') || (c == '\'')) && (buf[0] == '(')) + { + // We probably have a valid raw string. determine + // the size, and read it. + // *FIX: This is memory inefficient. + auto len = strtol(buf + 1, NULL, 0); + if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE; + std::vector buf; + if(len) + { + buf.resize(len); + count += fullread(istr, (char *)&buf[0], len); + value.assign(buf.begin(), buf.end()); + } + c = istr.get(); + ++count; + if(!((c == '"') || (c == '\''))) + { + return LLSDParser::PARSE_FAILURE; + } + } + else + { + return LLSDParser::PARSE_FAILURE; + } + return count; +} + +static const char* NOTATION_STRING_CHARACTERS[256] = +{ + "\\x00", // 0 + "\\x01", // 1 + "\\x02", // 2 + "\\x03", // 3 + "\\x04", // 4 + "\\x05", // 5 + "\\x06", // 6 + "\\a", // 7 + "\\b", // 8 + "\\t", // 9 + "\\n", // 10 + "\\v", // 11 + "\\f", // 12 + "\\r", // 13 + "\\x0e", // 14 + "\\x0f", // 15 + "\\x10", // 16 + "\\x11", // 17 + "\\x12", // 18 + "\\x13", // 19 + "\\x14", // 20 + "\\x15", // 21 + "\\x16", // 22 + "\\x17", // 23 + "\\x18", // 24 + "\\x19", // 25 + "\\x1a", // 26 + "\\x1b", // 27 + "\\x1c", // 28 + "\\x1d", // 29 + "\\x1e", // 30 + "\\x1f", // 31 + " ", // 32 + "!", // 33 + "\"", // 34 + "#", // 35 + "$", // 36 + "%", // 37 + "&", // 38 + "\\'", // 39 + "(", // 40 + ")", // 41 + "*", // 42 + "+", // 43 + ",", // 44 + "-", // 45 + ".", // 46 + "/", // 47 + "0", // 48 + "1", // 49 + "2", // 50 + "3", // 51 + "4", // 52 + "5", // 53 + "6", // 54 + "7", // 55 + "8", // 56 + "9", // 57 + ":", // 58 + ";", // 59 + "<", // 60 + "=", // 61 + ">", // 62 + "?", // 63 + "@", // 64 + "A", // 65 + "B", // 66 + "C", // 67 + "D", // 68 + "E", // 69 + "F", // 70 + "G", // 71 + "H", // 72 + "I", // 73 + "J", // 74 + "K", // 75 + "L", // 76 + "M", // 77 + "N", // 78 + "O", // 79 + "P", // 80 + "Q", // 81 + "R", // 82 + "S", // 83 + "T", // 84 + "U", // 85 + "V", // 86 + "W", // 87 + "X", // 88 + "Y", // 89 + "Z", // 90 + "[", // 91 + "\\\\", // 92 + "]", // 93 + "^", // 94 + "_", // 95 + "`", // 96 + "a", // 97 + "b", // 98 + "c", // 99 + "d", // 100 + "e", // 101 + "f", // 102 + "g", // 103 + "h", // 104 + "i", // 105 + "j", // 106 + "k", // 107 + "l", // 108 + "m", // 109 + "n", // 110 + "o", // 111 + "p", // 112 + "q", // 113 + "r", // 114 + "s", // 115 + "t", // 116 + "u", // 117 + "v", // 118 + "w", // 119 + "x", // 120 + "y", // 121 + "z", // 122 + "{", // 123 + "|", // 124 + "}", // 125 + "~", // 126 + "\\x7f", // 127 + "\\x80", // 128 + "\\x81", // 129 + "\\x82", // 130 + "\\x83", // 131 + "\\x84", // 132 + "\\x85", // 133 + "\\x86", // 134 + "\\x87", // 135 + "\\x88", // 136 + "\\x89", // 137 + "\\x8a", // 138 + "\\x8b", // 139 + "\\x8c", // 140 + "\\x8d", // 141 + "\\x8e", // 142 + "\\x8f", // 143 + "\\x90", // 144 + "\\x91", // 145 + "\\x92", // 146 + "\\x93", // 147 + "\\x94", // 148 + "\\x95", // 149 + "\\x96", // 150 + "\\x97", // 151 + "\\x98", // 152 + "\\x99", // 153 + "\\x9a", // 154 + "\\x9b", // 155 + "\\x9c", // 156 + "\\x9d", // 157 + "\\x9e", // 158 + "\\x9f", // 159 + "\\xa0", // 160 + "\\xa1", // 161 + "\\xa2", // 162 + "\\xa3", // 163 + "\\xa4", // 164 + "\\xa5", // 165 + "\\xa6", // 166 + "\\xa7", // 167 + "\\xa8", // 168 + "\\xa9", // 169 + "\\xaa", // 170 + "\\xab", // 171 + "\\xac", // 172 + "\\xad", // 173 + "\\xae", // 174 + "\\xaf", // 175 + "\\xb0", // 176 + "\\xb1", // 177 + "\\xb2", // 178 + "\\xb3", // 179 + "\\xb4", // 180 + "\\xb5", // 181 + "\\xb6", // 182 + "\\xb7", // 183 + "\\xb8", // 184 + "\\xb9", // 185 + "\\xba", // 186 + "\\xbb", // 187 + "\\xbc", // 188 + "\\xbd", // 189 + "\\xbe", // 190 + "\\xbf", // 191 + "\\xc0", // 192 + "\\xc1", // 193 + "\\xc2", // 194 + "\\xc3", // 195 + "\\xc4", // 196 + "\\xc5", // 197 + "\\xc6", // 198 + "\\xc7", // 199 + "\\xc8", // 200 + "\\xc9", // 201 + "\\xca", // 202 + "\\xcb", // 203 + "\\xcc", // 204 + "\\xcd", // 205 + "\\xce", // 206 + "\\xcf", // 207 + "\\xd0", // 208 + "\\xd1", // 209 + "\\xd2", // 210 + "\\xd3", // 211 + "\\xd4", // 212 + "\\xd5", // 213 + "\\xd6", // 214 + "\\xd7", // 215 + "\\xd8", // 216 + "\\xd9", // 217 + "\\xda", // 218 + "\\xdb", // 219 + "\\xdc", // 220 + "\\xdd", // 221 + "\\xde", // 222 + "\\xdf", // 223 + "\\xe0", // 224 + "\\xe1", // 225 + "\\xe2", // 226 + "\\xe3", // 227 + "\\xe4", // 228 + "\\xe5", // 229 + "\\xe6", // 230 + "\\xe7", // 231 + "\\xe8", // 232 + "\\xe9", // 233 + "\\xea", // 234 + "\\xeb", // 235 + "\\xec", // 236 + "\\xed", // 237 + "\\xee", // 238 + "\\xef", // 239 + "\\xf0", // 240 + "\\xf1", // 241 + "\\xf2", // 242 + "\\xf3", // 243 + "\\xf4", // 244 + "\\xf5", // 245 + "\\xf6", // 246 + "\\xf7", // 247 + "\\xf8", // 248 + "\\xf9", // 249 + "\\xfa", // 250 + "\\xfb", // 251 + "\\xfc", // 252 + "\\xfd", // 253 + "\\xfe", // 254 + "\\xff" // 255 +}; + +void serialize_string(const std::string& value, std::ostream& str) +{ + std::string::const_iterator it = value.begin(); + std::string::const_iterator end = value.end(); + U8 c; + for(; it != end; ++it) + { + c = (U8)(*it); + str << NOTATION_STRING_CHARACTERS[c]; + } +} + +llssize deserialize_boolean( + std::istream& istr, + LLSD& data, + const std::string& compare, + bool value) +{ + // + // this method is a little goofy, because it gets the stream at + // the point where the t or f has already been + // consumed. Basically, parse for a patch to the string passed in + // starting at index 1. If it's a match: + // * assign data to value + // * return the number of bytes read + // otherwise: + // * set data to LLSD::null + // * return LLSDParser::PARSE_FAILURE (-1) + // + llssize bytes_read = 0; + std::string::size_type ii = 0; + char c = istr.peek(); + while((++ii < compare.size()) + && (tolower(c) == (int)compare[ii]) + && istr.good()) + { + istr.ignore(); + ++bytes_read; + c = istr.peek(); + } + if(compare.size() != ii) + { + data.clear(); + return LLSDParser::PARSE_FAILURE; + } + data = value; + return bytes_read; +} + +std::ostream& operator<<(std::ostream& s, const LLSD& llsd) +{ + s << LLSDNotationStreamer(llsd); + return s; +} + + +//dirty little zippers -- yell at davep if these are horrid + +//return a string containing gzipped bytes of binary serialized LLSD +// VERY inefficient -- creates several copies of LLSD block in memory +std::string zip_llsd(LLSD& data) +{ + std::stringstream llsd_strm; + + LLSDSerialize::toBinary(data, llsd_strm); + + const U32 CHUNK = 65536; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION); + if (ret != Z_OK) + { + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + + std::string source = llsd_strm.str(); + + U8 out[CHUNK]; + + strm.avail_in = narrow(source.size()); + strm.next_in = (U8*) source.data(); + U8* output = NULL; + + U32 cur_size = 0; + + U32 have = 0; + + do + { + strm.avail_out = CHUNK; + strm.next_out = out; + + ret = deflate(&strm, Z_FINISH); + if (ret == Z_OK || ret == Z_STREAM_END) + { //copy result into output + if (strm.avail_out >= CHUNK) + { + deflateEnd(&strm); + if(output) + free(output); + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + + have = CHUNK-strm.avail_out; + U8* new_output = (U8*) realloc(output, cur_size+have); + if (new_output == NULL) + { + LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL; + deflateEnd(&strm); + if (output) + { + free(output); + } + return std::string(); + } + output = new_output; + memcpy(output+cur_size, out, have); + cur_size += have; + } + else + { + deflateEnd(&strm); + if(output) + free(output); + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + } + while (ret == Z_OK); + + std::string::size_type size = cur_size; + + std::string result((char*) output, size); + deflateEnd(&strm); + if(output) + free(output); + + return result; +} + +//decompress a block of LLSD from provided istream +// not very efficient -- creats a copy of decompressed LLSD block in memory +// and deserializes from that copy using LLSDSerialize +LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size) +{ + std::unique_ptr in = std::unique_ptr(new(std::nothrow) U8[size]); + if (!in) + { + return ZR_MEM_ERROR; + } + is.read((char*) in.get(), size); + + return unzip_llsd(data, in.get(), size); +} + +LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size) +{ + U8* result = NULL; + llssize cur_size = 0; + z_stream strm; + + constexpr U32 CHUNK = 1024 * 512; + + static thread_local std::unique_ptr out; + if (!out) + { + out = std::unique_ptr(new(std::nothrow) U8[CHUNK]); + } + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = size; + strm.next_in = const_cast(in); + + S32 ret = inflateInit(&strm); + + do + { + strm.avail_out = CHUNK; + strm.next_out = out.get(); + ret = inflate(&strm, Z_NO_FLUSH); + switch (ret) + { + case Z_NEED_DICT: + case Z_DATA_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_DATA_ERROR; + } + case Z_STREAM_ERROR: + case Z_BUF_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_BUFFER_ERROR; + } + + case Z_MEM_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_MEM_ERROR; + } + } + + U32 have = CHUNK-strm.avail_out; + + U8* new_result = (U8*)realloc(result, cur_size + have); + if (new_result == NULL) + { + inflateEnd(&strm); + if (result) + { + free(result); + } + return ZR_MEM_ERROR; + } + result = new_result; + memcpy(result+cur_size, out.get(), have); + cur_size += have; + + } while (ret == Z_OK && ret != Z_STREAM_END); + + inflateEnd(&strm); + + if (ret != Z_STREAM_END) + { + free(result); + return ZR_DATA_ERROR; + } + + //result now points to the decompressed LLSD block + { + char* result_ptr = strip_deprecated_header((char*)result, cur_size); + + boost::iostreams::stream istrm(result_ptr, cur_size); + + if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH)) + { + free(result); + return ZR_PARSE_ERROR; + } + } + + free(result); + return ZR_OK; +} +//This unzip function will only work with a gzip header and trailer - while the contents +//of the actual compressed data is the same for either format (gzip vs zlib ), the headers +//and trailers are different for the formats. +U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size ) +{ + if (size == 0) + { + LL_WARNS() << "No data to unzip." << LL_ENDL; + return NULL; + } + + U8* result = NULL; + U32 cur_size = 0; + z_stream strm; + + const U32 CHUNK = 0x4000; + + U8 *in = new(std::nothrow) U8[size]; + if (in == NULL) + { + LL_WARNS() << "Memory allocation failure." << LL_ENDL; + return NULL; + } + is.read((char*) in, size); + + U8 out[CHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = size; + strm.next_in = in; + + + S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP ); + do + { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) + { + inflateEnd(&strm); + free(result); + delete [] in; + valid = false; + } + + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + free(result); + delete [] in; + valid = false; + break; + } + + U32 have = CHUNK-strm.avail_out; + + U8* new_result = (U8*) realloc(result, cur_size + have); + if (new_result == NULL) + { + LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size + << " bytes; requested " << cur_size + have + << " bytes; total syze: ." << size << " bytes." + << LL_ENDL; + inflateEnd(&strm); + if (result) + { + free(result); + } + delete[] in; + valid = false; + return NULL; + } + result = new_result; + memcpy(result+cur_size, out, have); + cur_size += have; + + } while (ret == Z_OK); + + inflateEnd(&strm); + delete [] in; + + if (ret != Z_STREAM_END) + { + free(result); + valid = false; + return NULL; + } + + //result now points to the decompressed LLSD block + { + outsize= cur_size; + valid = true; + } + + return result; +} + +char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size) +{ + const char* deprecated_header = ""; + constexpr size_t deprecated_header_size = 17; + + if (cur_size > deprecated_header_size + && memcmp(in, deprecated_header, deprecated_header_size) == 0) + { + in = in + deprecated_header_size; + cur_size = cur_size - deprecated_header_size; + if (header_size) + { + *header_size = deprecated_header_size + 1; + } + } + + return in; +} + diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 4dd47ad1ac..dd3a58c26d 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1,1083 +1,1083 @@ -/** - * @file llsdutil.cpp - * @author Phoenix - * @date 2006-05-24 - * @brief Implementation of classes, functions, etc, for using structured data. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llsdutil.h" -#include - -#if LL_WINDOWS -# define WIN32_LEAN_AND_MEAN -# include // for htonl -#elif LL_LINUX -# include -#elif LL_DARWIN -# include -#endif - -#include "llsdserialize.h" -#include "stringize.h" -#include "is_approx_equal_fraction.h" - -#include -#include -#include - -// U32 -LLSD ll_sd_from_U32(const U32 val) -{ - std::vector v; - U32 net_order = htonl(val); - - v.resize(4); - memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U32 ll_U32_from_sd(const LLSD& sd) -{ - U32 ret; - std::vector v = sd.asBinary(); - if (v.size() < 4) - { - return 0; - } - memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ - ret = ntohl(ret); - return ret; -} - -//U64 -LLSD ll_sd_from_U64(const U64 val) -{ - std::vector v; - U32 high, low; - - high = (U32)(val >> 32); - low = (U32)val; - high = htonl(high); - low = htonl(low); - - v.resize(8); - memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */ - memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U64 ll_U64_from_sd(const LLSD& sd) -{ - U32 high, low; - std::vector v = sd.asBinary(); - - if (v.size() < 8) - { - return 0; - } - - memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */ - memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */ - high = ntohl(high); - low = ntohl(low); - - return ((U64)high) << 32 | low; -} - -// IP Address (stored in net order in a U32, so don't need swizzling) -LLSD ll_sd_from_ipaddr(const U32 val) -{ - std::vector v; - - v.resize(4); - memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U32 ll_ipaddr_from_sd(const LLSD& sd) -{ - U32 ret; - std::vector v = sd.asBinary(); - if (v.size() < 4) - { - return 0; - } - memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ - return ret; -} - -// Converts an LLSD binary to an LLSD string -LLSD ll_string_from_binary(const LLSD& sd) -{ - std::vector value = sd.asBinary(); - std::string str; - str.resize(value.size()); - memcpy(&str[0], &value[0], value.size()); - return str; -} - -// Converts an LLSD string to an LLSD binary -LLSD ll_binary_from_string(const LLSD& sd) -{ - std::vector binary_value; - - std::string string_value = sd.asString(); - for (const U8 c : string_value) - { - binary_value.push_back(c); - } - - binary_value.push_back('\0'); - - return binary_value; -} - -char* ll_print_sd(const LLSD& sd) -{ - const U32 bufferSize = 10 * 1024; - static char buffer[bufferSize]; - std::ostringstream stream; - //stream.rdbuf()->pubsetbuf(buffer, bufferSize); - stream << LLSDOStreamer(sd); - stream << std::ends; - strncpy(buffer, stream.str().c_str(), bufferSize); - buffer[bufferSize - 1] = '\0'; - return buffer; -} - -char* ll_pretty_print_sd_ptr(const LLSD* sd) -{ - if (sd) - { - return ll_pretty_print_sd(*sd); - } - return NULL; -} - -char* ll_pretty_print_sd(const LLSD& sd) -{ - const U32 bufferSize = 100 * 1024; - static char buffer[bufferSize]; - std::ostringstream stream; - //stream.rdbuf()->pubsetbuf(buffer, bufferSize); - stream << LLSDOStreamer(sd, LLSDFormatter::OPTIONS_PRETTY); - stream << std::ends; - strncpy(buffer, stream.str().c_str(), bufferSize); - buffer[bufferSize - 1] = '\0'; - return buffer; -} - -std::string ll_stream_notation_sd(const LLSD& sd) -{ - std::ostringstream stream; - stream << LLSDOStreamer(sd); - return stream.str(); -} - - -//compares the structure of an LLSD to a template LLSD and stores the -//"valid" values in a 3rd LLSD. Default values pulled from the template -//if the tested LLSD does not contain the key/value pair. -//Excess values in the test LLSD are ignored in the resultant_llsd. -//If the llsd to test has a specific key to a map and the values -//are not of the same type, false is returned or if the LLSDs are not -//of the same value. Ordering of arrays matters -//Otherwise, returns true -bool compare_llsd_with_template( - const LLSD& llsd_to_test, - const LLSD& template_llsd, - LLSD& resultant_llsd) -{ - LL_PROFILE_ZONE_SCOPED - - if ( - llsd_to_test.isUndefined() && - template_llsd.isDefined() ) - { - resultant_llsd = template_llsd; - return true; - } - else if ( llsd_to_test.type() != template_llsd.type() ) - { - resultant_llsd = LLSD(); - return false; - } - - if ( llsd_to_test.isArray() ) - { - //they are both arrays - //we loop over all the items in the template - //verifying that the to_test has a subset (in the same order) - //any shortcoming in the testing_llsd are just taken - //to be the rest of the template - LLSD data; - LLSD::array_const_iterator test_iter; - LLSD::array_const_iterator template_iter; - - resultant_llsd = LLSD::emptyArray(); - test_iter = llsd_to_test.beginArray(); - - for ( - template_iter = template_llsd.beginArray(); - (template_iter != template_llsd.endArray() && - test_iter != llsd_to_test.endArray()); - ++template_iter) - { - if ( !compare_llsd_with_template( - *test_iter, - *template_iter, - data) ) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd.append(data); - } - - ++test_iter; - } - - //so either the test or the template ended - //we do another loop now to the end of the template - //grabbing the default values - for (; - template_iter != template_llsd.endArray(); - ++template_iter) - { - resultant_llsd.append(*template_iter); - } - } - else if ( llsd_to_test.isMap() ) - { - //now we loop over the keys of the two maps - //any excess is taken from the template - //excess is ignored in the test - LLSD value; - LLSD::map_const_iterator template_iter; - - resultant_llsd = LLSD::emptyMap(); - for ( - template_iter = template_llsd.beginMap(); - template_iter != template_llsd.endMap(); - ++template_iter) - { - if ( llsd_to_test.has(template_iter->first) ) - { - //the test LLSD has the same key - if ( !compare_llsd_with_template( - llsd_to_test[template_iter->first], - template_iter->second, - value) ) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd[template_iter->first] = value; - } - } - else - { - //test llsd doesn't have it...take the - //template as default value - resultant_llsd[template_iter->first] = - template_iter->second; - } - } - } - else - { - //of same type...take the test llsd's value - resultant_llsd = llsd_to_test; - } - - - return true; -} - -// filter_llsd_with_template() is a direct clone (copy-n-paste) of -// compare_llsd_with_template with the following differences: -// (1) bool vs BOOL return types -// (2) A map with the key value "*" is a special value and maps any key in the -// test llsd that doesn't have an explicitly matching key in the template. -// (3) The element of an array with exactly one element is taken as a template -// for *all* the elements of the test array. If the template array is of -// different size, compare_llsd_with_template() semantics apply. -bool filter_llsd_with_template( - const LLSD & llsd_to_test, - const LLSD & template_llsd, - LLSD & resultant_llsd) -{ - LL_PROFILE_ZONE_SCOPED - - if (llsd_to_test.isUndefined() && template_llsd.isDefined()) - { - resultant_llsd = template_llsd; - return true; - } - else if (llsd_to_test.type() != template_llsd.type()) - { - resultant_llsd = LLSD(); - return false; - } - - if (llsd_to_test.isArray()) - { - //they are both arrays - //we loop over all the items in the template - //verifying that the to_test has a subset (in the same order) - //any shortcoming in the testing_llsd are just taken - //to be the rest of the template - LLSD data; - LLSD::array_const_iterator test_iter; - LLSD::array_const_iterator template_iter; - - resultant_llsd = LLSD::emptyArray(); - test_iter = llsd_to_test.beginArray(); - - if (1 == template_llsd.size()) - { - // If the template has a single item, treat it as - // the template for *all* items in the test LLSD. - template_iter = template_llsd.beginArray(); - - for (; test_iter != llsd_to_test.endArray(); ++test_iter) - { - if (! filter_llsd_with_template(*test_iter, *template_iter, data)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd.append(data); - } - } - } - else - { - // Traditional compare_llsd_with_template matching - - for (template_iter = template_llsd.beginArray(); - template_iter != template_llsd.endArray() && - test_iter != llsd_to_test.endArray(); - ++template_iter, ++test_iter) - { - if (! filter_llsd_with_template(*test_iter, *template_iter, data)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd.append(data); - } - } - - //so either the test or the template ended - //we do another loop now to the end of the template - //grabbing the default values - for (; - template_iter != template_llsd.endArray(); - ++template_iter) - { - resultant_llsd.append(*template_iter); - } - } - } - else if (llsd_to_test.isMap()) - { - resultant_llsd = LLSD::emptyMap(); - - //now we loop over the keys of the two maps - //any excess is taken from the template - //excess is ignored in the test - - // Special tag for wildcarded LLSD map key templates - const LLSD::String wildcard_tag("*"); - - const bool template_has_wildcard = template_llsd.has(wildcard_tag); - LLSD wildcard_value; - LLSD value; - - const LLSD::map_const_iterator template_iter_end(template_llsd.endMap()); - for (LLSD::map_const_iterator template_iter(template_llsd.beginMap()); - template_iter_end != template_iter; - ++template_iter) - { - if (wildcard_tag == template_iter->first) - { - wildcard_value = template_iter->second; - } - else if (llsd_to_test.has(template_iter->first)) - { - //the test LLSD has the same key - if (! filter_llsd_with_template(llsd_to_test[template_iter->first], - template_iter->second, - value)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd[template_iter->first] = value; - } - } - else if (! template_has_wildcard) - { - // test llsd doesn't have it...take the - // template as default value - resultant_llsd[template_iter->first] = template_iter->second; - } - } - if (template_has_wildcard) - { - LLSD sub_value; - LLSD::map_const_iterator test_iter; - - for (test_iter = llsd_to_test.beginMap(); - test_iter != llsd_to_test.endMap(); - ++test_iter) - { - if (resultant_llsd.has(test_iter->first)) - { - // Final value has test key, assume more specific - // template matched and we shouldn't modify it again. - continue; - } - else if (! filter_llsd_with_template(test_iter->second, - wildcard_value, - sub_value)) - { - // Test value doesn't match wildcarded template - resultant_llsd = LLSD(); - return false; - } - else - { - // Test value matches template, add the actuals. - resultant_llsd[test_iter->first] = sub_value; - } - } - } - } - else - { - //of same type...take the test llsd's value - resultant_llsd = llsd_to_test; - } - - return true; -} - -/***************************************************************************** -* Helpers for llsd_matches() -*****************************************************************************/ -// raw data used for LLSD::Type lookup -struct Data -{ - LLSD::Type type; - const char* name; -} typedata[] = -{ -#define def(type) { LLSD::type, &#type[4] } - def(TypeUndefined), - def(TypeBoolean), - def(TypeInteger), - def(TypeReal), - def(TypeString), - def(TypeUUID), - def(TypeDate), - def(TypeURI), - def(TypeBinary), - def(TypeMap), - def(TypeArray) -#undef def -}; - -// LLSD::Type lookup class into which we load the above static data -class TypeLookup -{ - typedef std::map MapType; - -public: - TypeLookup() - { - LL_PROFILE_ZONE_SCOPED - - for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) - { - mMap[di->type] = di->name; - } - } - - std::string lookup(LLSD::Type type) const - { - LL_PROFILE_ZONE_SCOPED - - MapType::const_iterator found = mMap.find(type); - if (found != mMap.end()) - { - return found->second; - } - return STRINGIZE(""); - } - -private: - MapType mMap; -}; - -// static instance of the lookup class -static const TypeLookup sTypes; - -// describe a mismatch; phrasing may want tweaking -const std::string op(" required instead of "); - -// llsd_matches() wants to identify specifically where in a complex prototype -// structure the mismatch occurred. This entails passing a prefix string, -// empty for the top-level call. If the prototype contains an array of maps, -// and the mismatch occurs in the second map in a key 'foo', we want to -// decorate the returned string with: "[1]['foo']: etc." On the other hand, we -// want to omit the entire prefix -- including colon -- if the mismatch is at -// top level. This helper accepts the (possibly empty) recursively-accumulated -// prefix string, returning either empty or the original string with colon -// appended. -static std::string colon(const std::string& pfx) -{ - if (pfx.empty()) - return pfx; - return pfx + ": "; -} - -// param type for match_types -typedef std::vector TypeVector; - -// The scalar cases in llsd_matches() use this helper. In most cases, we can -// accept not only the exact type specified in the prototype, but also other -// types convertible to the expected type. That implies looping over an array -// of such types. If the actual type doesn't match any of them, we want to -// provide a list of acceptable conversions as well as the exact type, e.g.: -// "Integer (or Boolean, Real, String) required instead of UUID". Both the -// implementation and the calling logic are simplified by separating out the -// expected type from the convertible types. -static std::string match_types(LLSD::Type expect, // prototype.type() - const TypeVector& accept, // types convertible to that type - LLSD::Type actual, // type we're checking - const std::string& pfx) // as for llsd_matches -{ - LL_PROFILE_ZONE_SCOPED - - // Trivial case: if the actual type is exactly what we expect, we're good. - if (actual == expect) - return ""; - - // For the rest of the logic, build up a suitable error string as we go so - // we only have to make a single pass over the list of acceptable types. - // If we detect success along the way, we'll simply discard the partial - // error string. - std::ostringstream out; - out << colon(pfx) << sTypes.lookup(expect); - - // If there are any convertible types, append that list. - if (! accept.empty()) - { - out << " ("; - const char* sep = "or "; - for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); - ai != aend; ++ai, sep = ", ") - { - // Don't forget to return success if we match any of those types... - if (actual == *ai) - return ""; - out << sep << sTypes.lookup(*ai); - } - out << ')'; - } - // If we got this far, it's because 'actual' was not one of the acceptable - // types, so we must return an error. 'out' already contains colon(pfx) - // and the formatted list of acceptable types, so just append the mismatch - // phrase and the actual type. - out << op << sTypes.lookup(actual); - return out.str(); -} - -// see docstring in .h file -std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) -{ - LL_PROFILE_ZONE_SCOPED - - // An undefined prototype means that any data is valid. - // An undefined slot in an array or map prototype means that any data - // may fill that slot. - if (prototype.isUndefined()) - return ""; - // A prototype array must match a data array with at least as many - // entries. Moreover, every prototype entry must match the - // corresponding data entry. - if (prototype.isArray()) - { - if (! data.isArray()) - { - return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); - } - if (data.size() < prototype.size()) - { - return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op - << "Array size " << data.size()); - } - for (LLSD::Integer i = 0; i < prototype.size(); ++i) - { - std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); - if (! match.empty()) - { - return match; - } - } - return ""; - } - // A prototype map must match a data map. Every key in the prototype - // must have a corresponding key in the data map; every value in the - // prototype must match the corresponding key's value in the data. - if (prototype.isMap()) - { - if (! data.isMap()) - { - return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); - } - // If there are a number of keys missing from the data, it would be - // frustrating to a coder to discover them one at a time, with a big - // build each time. Enumerate all missing keys. - std::ostringstream out; - out << colon(pfx); - const char* init = "Map missing keys: "; - const char* sep = init; - for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) - { - if (! data.has(mi->first)) - { - out << sep << mi->first; - sep = ", "; - } - } - // So... are we missing any keys? - if (sep != init) - { - return out.str(); - } - // Good, the data block contains all the keys required by the - // prototype. Now match the prototype entries. - for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) - { - std::string match(llsd_matches(mi2->second, data[mi2->first], - STRINGIZE("['" << mi2->first << "']"))); - if (! match.empty()) - { - return match; - } - } - return ""; - } - // A String prototype can match String, Boolean, Integer, Real, UUID, - // Date and URI, because any of these can be converted to String. - if (prototype.isString()) - { - static LLSD::Type accept[] = - { - LLSD::TypeBoolean, - LLSD::TypeInteger, - LLSD::TypeReal, - LLSD::TypeUUID, - LLSD::TypeDate, - LLSD::TypeURI - }; - return match_types(prototype.type(), - TypeVector(boost::begin(accept), boost::end(accept)), - data.type(), - pfx); - } - // Boolean, Integer, Real match each other or String. TBD: ensure that - // a String value is numeric. - if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) - { - static LLSD::Type all[] = - { - LLSD::TypeBoolean, - LLSD::TypeInteger, - LLSD::TypeReal, - LLSD::TypeString - }; - // Funny business: shuffle the set of acceptable types to include all - // but the prototype's type. Get the acceptable types in a set. - std::set rest(boost::begin(all), boost::end(all)); - // Remove the prototype's type because we pass that separately. - rest.erase(prototype.type()); - return match_types(prototype.type(), - TypeVector(rest.begin(), rest.end()), - data.type(), - pfx); - } - // UUID, Date and URI match themselves or String. - if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) - { - static LLSD::Type accept[] = - { - LLSD::TypeString - }; - return match_types(prototype.type(), - TypeVector(boost::begin(accept), boost::end(accept)), - data.type(), - pfx); - } - // We don't yet know the conversion semantics associated with any new LLSD - // data type that might be added, so until we've been extended to handle - // them, assume it's strict: the new type matches only itself. (This is - // true of Binary, which is why we don't handle that case separately.) Too - // bad LLSD doesn't define isConvertible(Type to, Type from). - return match_types(prototype.type(), TypeVector(), data.type(), pfx); -} - -bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits) -{ - LL_PROFILE_ZONE_SCOPED - - // We're comparing strict equality of LLSD representation rather than - // performing any conversions. So if the types aren't equal, the LLSD - // values aren't equal. - if (lhs.type() != rhs.type()) - { - return false; - } - - // Here we know both types are equal. Now compare values. - switch (lhs.type()) - { - case LLSD::TypeUndefined: - // Both are TypeUndefined. There's nothing more to know. - return true; - - case LLSD::TypeReal: - // This is where the 'bits' argument comes in handy. If passed - // explicitly, it means to use is_approx_equal_fraction() to compare. - if (bits >= 0) - { - return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); - } - // Otherwise we compare bit representations, and the usual caveats - // about comparing floating-point numbers apply. Omitting 'bits' when - // comparing Real values is only useful when we expect identical bit - // representation for a given Real value, e.g. for integer-valued - // Reals. - return (lhs.asReal() == rhs.asReal()); - -#define COMPARE_SCALAR(type) \ - case LLSD::Type##type: \ - /* LLSD::URI has operator!=() but not operator==() */ \ - /* rely on the optimizer for all others */ \ - return (! (lhs.as##type() != rhs.as##type())) - - COMPARE_SCALAR(Boolean); - COMPARE_SCALAR(Integer); - COMPARE_SCALAR(String); - COMPARE_SCALAR(UUID); - COMPARE_SCALAR(Date); - COMPARE_SCALAR(URI); - COMPARE_SCALAR(Binary); - -#undef COMPARE_SCALAR - - case LLSD::TypeArray: - { - LLSD::array_const_iterator - lai(lhs.beginArray()), laend(lhs.endArray()), - rai(rhs.beginArray()), raend(rhs.endArray()); - // Compare array elements, walking the two arrays in parallel. - for ( ; lai != laend && rai != raend; ++lai, ++rai) - { - // If any one array element is unequal, the arrays are unequal. - if (! llsd_equals(*lai, *rai, bits)) - return false; - } - // Here we've reached the end of one or the other array. They're equal - // only if they're BOTH at end: that is, if they have equal length too. - return (lai == laend && rai == raend); - } - - case LLSD::TypeMap: - { - // Build a set of all rhs keys. - std::set rhskeys; - for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); - rmi != rmend; ++rmi) - { - rhskeys.insert(rmi->first); - } - // Now walk all the lhs keys. - for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); - lmi != lmend; ++lmi) - { - // Try to erase this lhs key from the set of rhs keys. If rhs has - // no such key, the maps are unequal. erase(key) returns count of - // items erased. - if (rhskeys.erase(lmi->first) != 1) - return false; - // Both maps have the current key. Compare values. - if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) - return false; - } - // We've now established that all the lhs keys have equal values in - // both maps. The maps are equal unless rhs contains a superset of - // those keys. - return rhskeys.empty(); - } - - default: - // We expect that every possible type() value is specifically handled - // above. Failing to extend this switch to support a new LLSD type is - // an error that must be brought to the coder's attention. - LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " - "unknown type " << lhs.type() << LL_ENDL; - return false; // pacify the compiler - } -} - -/***************************************************************************** -* llsd::drill() -*****************************************************************************/ -namespace llsd -{ - -LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) -{ - LL_PROFILE_ZONE_SCOPED - - // Treat rawPath uniformly as an array. If it's not already an array, - // store it as the only entry in one. (But let's say Undefined means an - // empty array.) - LLSD path; - if (rawPath.isArray() || rawPath.isUndefined()) - { - path = rawPath; - } - else - { - path.append(rawPath); - } - - // Need to indicate a current destination -- but that current destination - // must change as we step through the path array. Where normally we'd use - // an LLSD& to capture a subscripted LLSD lvalue, this time we must - // instead use a pointer -- since it must be reassigned. - // Start by pointing to the input blob exactly as is. - LLSD* located{&blob}; - - // Extract the element of interest by walking path. Use an explicit index - // so that, in case of a bogus type in path, we can identify the specific - // path entry that's bad. - for (LLSD::Integer i = 0; i < path.size(); ++i) - { - LL_PROFILE_ZONE_NUM( i ) - - const LLSD& key{path[i]}; - if (key.isString()) - { - // a string path element is a map key - located = &((*located)[key.asString()]); - } - else if (key.isInteger()) - { - // an integer path element is an array index - located = &((*located)[key.asInteger()]); - } - else - { - // What do we do with Real or Array or Map or ...? - // As it's a coder error -- not a user error -- rub the coder's - // face in it so it gets fixed. - LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath - << "): path[" << i << "] bad type " - << sTypes.lookup(key.type()) << LL_ENDL; - } - } - - // dereference the pointer to return a reference to the element we found - return *located; -} - -LLSD drill(const LLSD& blob, const LLSD& path) -{ - LL_PROFILE_ZONE_SCOPED - - // drill_ref() does exactly what we want. Temporarily cast away - // const-ness and use that. - return drill_ref(const_cast(blob), path); -} - -} // namespace llsd - -// Construct a deep partial clone of of an LLSD object. primitive types share -// references, however maps, arrays and binary objects are duplicated. An optional -// filter may be include to exclude/include keys in a map. -LLSD llsd_clone(LLSD value, LLSD filter) -{ - LL_PROFILE_ZONE_SCOPED - - LLSD clone; - bool has_filter(filter.isMap()); - - switch (value.type()) - { - case LLSD::TypeMap: - clone = LLSD::emptyMap(); - for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) - { - if (has_filter) - { - if (filter.has((*itm).first)) - { - if (!filter[(*itm).first].asBoolean()) - continue; - } - else if (filter.has("*")) - { - if (!filter["*"].asBoolean()) - continue; - } - else - { - continue; - } - } - clone[(*itm).first] = llsd_clone((*itm).second, filter); - } - break; - case LLSD::TypeArray: - clone = LLSD::emptyArray(); - for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) - { - clone.append(llsd_clone(*ita, filter)); - } - break; - - case LLSD::TypeBinary: - { - LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end()); - clone = LLSD::Binary(bin); - break; - } - default: - clone = value; - } - - return clone; -} - -LLSD llsd_shallow(LLSD value, LLSD filter) -{ - LLSD shallow; - bool has_filter(filter.isMap()); - - if (value.isMap()) - { - shallow = LLSD::emptyMap(); - for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) - { - if (has_filter) - { - if (filter.has((*itm).first)) - { - if (!filter[(*itm).first].asBoolean()) - continue; - } - else if (filter.has("*")) - { - if (!filter["*"].asBoolean()) - continue; - } - else - { - continue; - } - } - shallow[(*itm).first] = (*itm).second; - } - } - else if (value.isArray()) - { - shallow = LLSD::emptyArray(); - for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) - { - shallow.append(*ita); - } - } - else - { - return value; - } - - return shallow; -} - -LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) -{ - // LLSD supports a number of types, two of which are aggregates: Map and - // Array. We don't try to support Map: supporting Map would seem to - // promise that we could somehow match the string key to 'func's parameter - // names. Uh sorry, maybe in some future version of C++ with reflection. - if (args.isMap()) - { - LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); - } - // We expect an LLSD array, but what the heck, treat isUndefined() as a - // zero-length array for calling a nullary 'func'. - if (args.isUndefined() || args.isArray()) - { - // this works because LLSD().size() == 0 - if (args.size() != arity) - { - LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", - args.size(), "-entry LLSD array)"))); - } - return args; - } - - // args is one of the scalar types - // scalar_LLSD.size() == 0, so don't test that here. - // You can pass a scalar LLSD only to a unary 'func'. - if (arity != 1) - { - LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " - "LLSD ", LLSD::typeString(args.type()), ")"))); - } - // make an array of it - return llsd::array(args); -} +/** + * @file llsdutil.cpp + * @author Phoenix + * @date 2006-05-24 + * @brief Implementation of classes, functions, etc, for using structured data. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llsdutil.h" +#include + +#if LL_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include // for htonl +#elif LL_LINUX +# include +#elif LL_DARWIN +# include +#endif + +#include "llsdserialize.h" +#include "stringize.h" +#include "is_approx_equal_fraction.h" + +#include +#include +#include + +// U32 +LLSD ll_sd_from_U32(const U32 val) +{ + std::vector v; + U32 net_order = htonl(val); + + v.resize(4); + memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U32 ll_U32_from_sd(const LLSD& sd) +{ + U32 ret; + std::vector v = sd.asBinary(); + if (v.size() < 4) + { + return 0; + } + memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ + ret = ntohl(ret); + return ret; +} + +//U64 +LLSD ll_sd_from_U64(const U64 val) +{ + std::vector v; + U32 high, low; + + high = (U32)(val >> 32); + low = (U32)val; + high = htonl(high); + low = htonl(low); + + v.resize(8); + memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */ + memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U64 ll_U64_from_sd(const LLSD& sd) +{ + U32 high, low; + std::vector v = sd.asBinary(); + + if (v.size() < 8) + { + return 0; + } + + memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */ + memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */ + high = ntohl(high); + low = ntohl(low); + + return ((U64)high) << 32 | low; +} + +// IP Address (stored in net order in a U32, so don't need swizzling) +LLSD ll_sd_from_ipaddr(const U32 val) +{ + std::vector v; + + v.resize(4); + memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U32 ll_ipaddr_from_sd(const LLSD& sd) +{ + U32 ret; + std::vector v = sd.asBinary(); + if (v.size() < 4) + { + return 0; + } + memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ + return ret; +} + +// Converts an LLSD binary to an LLSD string +LLSD ll_string_from_binary(const LLSD& sd) +{ + std::vector value = sd.asBinary(); + std::string str; + str.resize(value.size()); + memcpy(&str[0], &value[0], value.size()); + return str; +} + +// Converts an LLSD string to an LLSD binary +LLSD ll_binary_from_string(const LLSD& sd) +{ + std::vector binary_value; + + std::string string_value = sd.asString(); + for (const U8 c : string_value) + { + binary_value.push_back(c); + } + + binary_value.push_back('\0'); + + return binary_value; +} + +char* ll_print_sd(const LLSD& sd) +{ + const U32 bufferSize = 10 * 1024; + static char buffer[bufferSize]; + std::ostringstream stream; + //stream.rdbuf()->pubsetbuf(buffer, bufferSize); + stream << LLSDOStreamer(sd); + stream << std::ends; + strncpy(buffer, stream.str().c_str(), bufferSize); + buffer[bufferSize - 1] = '\0'; + return buffer; +} + +char* ll_pretty_print_sd_ptr(const LLSD* sd) +{ + if (sd) + { + return ll_pretty_print_sd(*sd); + } + return NULL; +} + +char* ll_pretty_print_sd(const LLSD& sd) +{ + const U32 bufferSize = 100 * 1024; + static char buffer[bufferSize]; + std::ostringstream stream; + //stream.rdbuf()->pubsetbuf(buffer, bufferSize); + stream << LLSDOStreamer(sd, LLSDFormatter::OPTIONS_PRETTY); + stream << std::ends; + strncpy(buffer, stream.str().c_str(), bufferSize); + buffer[bufferSize - 1] = '\0'; + return buffer; +} + +std::string ll_stream_notation_sd(const LLSD& sd) +{ + std::ostringstream stream; + stream << LLSDOStreamer(sd); + return stream.str(); +} + + +//compares the structure of an LLSD to a template LLSD and stores the +//"valid" values in a 3rd LLSD. Default values pulled from the template +//if the tested LLSD does not contain the key/value pair. +//Excess values in the test LLSD are ignored in the resultant_llsd. +//If the llsd to test has a specific key to a map and the values +//are not of the same type, false is returned or if the LLSDs are not +//of the same value. Ordering of arrays matters +//Otherwise, returns true +bool compare_llsd_with_template( + const LLSD& llsd_to_test, + const LLSD& template_llsd, + LLSD& resultant_llsd) +{ + LL_PROFILE_ZONE_SCOPED + + if ( + llsd_to_test.isUndefined() && + template_llsd.isDefined() ) + { + resultant_llsd = template_llsd; + return true; + } + else if ( llsd_to_test.type() != template_llsd.type() ) + { + resultant_llsd = LLSD(); + return false; + } + + if ( llsd_to_test.isArray() ) + { + //they are both arrays + //we loop over all the items in the template + //verifying that the to_test has a subset (in the same order) + //any shortcoming in the testing_llsd are just taken + //to be the rest of the template + LLSD data; + LLSD::array_const_iterator test_iter; + LLSD::array_const_iterator template_iter; + + resultant_llsd = LLSD::emptyArray(); + test_iter = llsd_to_test.beginArray(); + + for ( + template_iter = template_llsd.beginArray(); + (template_iter != template_llsd.endArray() && + test_iter != llsd_to_test.endArray()); + ++template_iter) + { + if ( !compare_llsd_with_template( + *test_iter, + *template_iter, + data) ) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + + ++test_iter; + } + + //so either the test or the template ended + //we do another loop now to the end of the template + //grabbing the default values + for (; + template_iter != template_llsd.endArray(); + ++template_iter) + { + resultant_llsd.append(*template_iter); + } + } + else if ( llsd_to_test.isMap() ) + { + //now we loop over the keys of the two maps + //any excess is taken from the template + //excess is ignored in the test + LLSD value; + LLSD::map_const_iterator template_iter; + + resultant_llsd = LLSD::emptyMap(); + for ( + template_iter = template_llsd.beginMap(); + template_iter != template_llsd.endMap(); + ++template_iter) + { + if ( llsd_to_test.has(template_iter->first) ) + { + //the test LLSD has the same key + if ( !compare_llsd_with_template( + llsd_to_test[template_iter->first], + template_iter->second, + value) ) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd[template_iter->first] = value; + } + } + else + { + //test llsd doesn't have it...take the + //template as default value + resultant_llsd[template_iter->first] = + template_iter->second; + } + } + } + else + { + //of same type...take the test llsd's value + resultant_llsd = llsd_to_test; + } + + + return true; +} + +// filter_llsd_with_template() is a direct clone (copy-n-paste) of +// compare_llsd_with_template with the following differences: +// (1) bool vs BOOL return types +// (2) A map with the key value "*" is a special value and maps any key in the +// test llsd that doesn't have an explicitly matching key in the template. +// (3) The element of an array with exactly one element is taken as a template +// for *all* the elements of the test array. If the template array is of +// different size, compare_llsd_with_template() semantics apply. +bool filter_llsd_with_template( + const LLSD & llsd_to_test, + const LLSD & template_llsd, + LLSD & resultant_llsd) +{ + LL_PROFILE_ZONE_SCOPED + + if (llsd_to_test.isUndefined() && template_llsd.isDefined()) + { + resultant_llsd = template_llsd; + return true; + } + else if (llsd_to_test.type() != template_llsd.type()) + { + resultant_llsd = LLSD(); + return false; + } + + if (llsd_to_test.isArray()) + { + //they are both arrays + //we loop over all the items in the template + //verifying that the to_test has a subset (in the same order) + //any shortcoming in the testing_llsd are just taken + //to be the rest of the template + LLSD data; + LLSD::array_const_iterator test_iter; + LLSD::array_const_iterator template_iter; + + resultant_llsd = LLSD::emptyArray(); + test_iter = llsd_to_test.beginArray(); + + if (1 == template_llsd.size()) + { + // If the template has a single item, treat it as + // the template for *all* items in the test LLSD. + template_iter = template_llsd.beginArray(); + + for (; test_iter != llsd_to_test.endArray(); ++test_iter) + { + if (! filter_llsd_with_template(*test_iter, *template_iter, data)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + } + } + else + { + // Traditional compare_llsd_with_template matching + + for (template_iter = template_llsd.beginArray(); + template_iter != template_llsd.endArray() && + test_iter != llsd_to_test.endArray(); + ++template_iter, ++test_iter) + { + if (! filter_llsd_with_template(*test_iter, *template_iter, data)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + } + + //so either the test or the template ended + //we do another loop now to the end of the template + //grabbing the default values + for (; + template_iter != template_llsd.endArray(); + ++template_iter) + { + resultant_llsd.append(*template_iter); + } + } + } + else if (llsd_to_test.isMap()) + { + resultant_llsd = LLSD::emptyMap(); + + //now we loop over the keys of the two maps + //any excess is taken from the template + //excess is ignored in the test + + // Special tag for wildcarded LLSD map key templates + const LLSD::String wildcard_tag("*"); + + const bool template_has_wildcard = template_llsd.has(wildcard_tag); + LLSD wildcard_value; + LLSD value; + + const LLSD::map_const_iterator template_iter_end(template_llsd.endMap()); + for (LLSD::map_const_iterator template_iter(template_llsd.beginMap()); + template_iter_end != template_iter; + ++template_iter) + { + if (wildcard_tag == template_iter->first) + { + wildcard_value = template_iter->second; + } + else if (llsd_to_test.has(template_iter->first)) + { + //the test LLSD has the same key + if (! filter_llsd_with_template(llsd_to_test[template_iter->first], + template_iter->second, + value)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd[template_iter->first] = value; + } + } + else if (! template_has_wildcard) + { + // test llsd doesn't have it...take the + // template as default value + resultant_llsd[template_iter->first] = template_iter->second; + } + } + if (template_has_wildcard) + { + LLSD sub_value; + LLSD::map_const_iterator test_iter; + + for (test_iter = llsd_to_test.beginMap(); + test_iter != llsd_to_test.endMap(); + ++test_iter) + { + if (resultant_llsd.has(test_iter->first)) + { + // Final value has test key, assume more specific + // template matched and we shouldn't modify it again. + continue; + } + else if (! filter_llsd_with_template(test_iter->second, + wildcard_value, + sub_value)) + { + // Test value doesn't match wildcarded template + resultant_llsd = LLSD(); + return false; + } + else + { + // Test value matches template, add the actuals. + resultant_llsd[test_iter->first] = sub_value; + } + } + } + } + else + { + //of same type...take the test llsd's value + resultant_llsd = llsd_to_test; + } + + return true; +} + +/***************************************************************************** +* Helpers for llsd_matches() +*****************************************************************************/ +// raw data used for LLSD::Type lookup +struct Data +{ + LLSD::Type type; + const char* name; +} typedata[] = +{ +#define def(type) { LLSD::type, &#type[4] } + def(TypeUndefined), + def(TypeBoolean), + def(TypeInteger), + def(TypeReal), + def(TypeString), + def(TypeUUID), + def(TypeDate), + def(TypeURI), + def(TypeBinary), + def(TypeMap), + def(TypeArray) +#undef def +}; + +// LLSD::Type lookup class into which we load the above static data +class TypeLookup +{ + typedef std::map MapType; + +public: + TypeLookup() + { + LL_PROFILE_ZONE_SCOPED + + for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) + { + mMap[di->type] = di->name; + } + } + + std::string lookup(LLSD::Type type) const + { + LL_PROFILE_ZONE_SCOPED + + MapType::const_iterator found = mMap.find(type); + if (found != mMap.end()) + { + return found->second; + } + return STRINGIZE(""); + } + +private: + MapType mMap; +}; + +// static instance of the lookup class +static const TypeLookup sTypes; + +// describe a mismatch; phrasing may want tweaking +const std::string op(" required instead of "); + +// llsd_matches() wants to identify specifically where in a complex prototype +// structure the mismatch occurred. This entails passing a prefix string, +// empty for the top-level call. If the prototype contains an array of maps, +// and the mismatch occurs in the second map in a key 'foo', we want to +// decorate the returned string with: "[1]['foo']: etc." On the other hand, we +// want to omit the entire prefix -- including colon -- if the mismatch is at +// top level. This helper accepts the (possibly empty) recursively-accumulated +// prefix string, returning either empty or the original string with colon +// appended. +static std::string colon(const std::string& pfx) +{ + if (pfx.empty()) + return pfx; + return pfx + ": "; +} + +// param type for match_types +typedef std::vector TypeVector; + +// The scalar cases in llsd_matches() use this helper. In most cases, we can +// accept not only the exact type specified in the prototype, but also other +// types convertible to the expected type. That implies looping over an array +// of such types. If the actual type doesn't match any of them, we want to +// provide a list of acceptable conversions as well as the exact type, e.g.: +// "Integer (or Boolean, Real, String) required instead of UUID". Both the +// implementation and the calling logic are simplified by separating out the +// expected type from the convertible types. +static std::string match_types(LLSD::Type expect, // prototype.type() + const TypeVector& accept, // types convertible to that type + LLSD::Type actual, // type we're checking + const std::string& pfx) // as for llsd_matches +{ + LL_PROFILE_ZONE_SCOPED + + // Trivial case: if the actual type is exactly what we expect, we're good. + if (actual == expect) + return ""; + + // For the rest of the logic, build up a suitable error string as we go so + // we only have to make a single pass over the list of acceptable types. + // If we detect success along the way, we'll simply discard the partial + // error string. + std::ostringstream out; + out << colon(pfx) << sTypes.lookup(expect); + + // If there are any convertible types, append that list. + if (! accept.empty()) + { + out << " ("; + const char* sep = "or "; + for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); + ai != aend; ++ai, sep = ", ") + { + // Don't forget to return success if we match any of those types... + if (actual == *ai) + return ""; + out << sep << sTypes.lookup(*ai); + } + out << ')'; + } + // If we got this far, it's because 'actual' was not one of the acceptable + // types, so we must return an error. 'out' already contains colon(pfx) + // and the formatted list of acceptable types, so just append the mismatch + // phrase and the actual type. + out << op << sTypes.lookup(actual); + return out.str(); +} + +// see docstring in .h file +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) +{ + LL_PROFILE_ZONE_SCOPED + + // An undefined prototype means that any data is valid. + // An undefined slot in an array or map prototype means that any data + // may fill that slot. + if (prototype.isUndefined()) + return ""; + // A prototype array must match a data array with at least as many + // entries. Moreover, every prototype entry must match the + // corresponding data entry. + if (prototype.isArray()) + { + if (! data.isArray()) + { + return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); + } + if (data.size() < prototype.size()) + { + return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op + << "Array size " << data.size()); + } + for (LLSD::Integer i = 0; i < prototype.size(); ++i) + { + std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A prototype map must match a data map. Every key in the prototype + // must have a corresponding key in the data map; every value in the + // prototype must match the corresponding key's value in the data. + if (prototype.isMap()) + { + if (! data.isMap()) + { + return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); + } + // If there are a number of keys missing from the data, it would be + // frustrating to a coder to discover them one at a time, with a big + // build each time. Enumerate all missing keys. + std::ostringstream out; + out << colon(pfx); + const char* init = "Map missing keys: "; + const char* sep = init; + for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) + { + if (! data.has(mi->first)) + { + out << sep << mi->first; + sep = ", "; + } + } + // So... are we missing any keys? + if (sep != init) + { + return out.str(); + } + // Good, the data block contains all the keys required by the + // prototype. Now match the prototype entries. + for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) + { + std::string match(llsd_matches(mi2->second, data[mi2->first], + STRINGIZE("['" << mi2->first << "']"))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A String prototype can match String, Boolean, Integer, Real, UUID, + // Date and URI, because any of these can be converted to String. + if (prototype.isString()) + { + static LLSD::Type accept[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeUUID, + LLSD::TypeDate, + LLSD::TypeURI + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // Boolean, Integer, Real match each other or String. TBD: ensure that + // a String value is numeric. + if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) + { + static LLSD::Type all[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeString + }; + // Funny business: shuffle the set of acceptable types to include all + // but the prototype's type. Get the acceptable types in a set. + std::set rest(boost::begin(all), boost::end(all)); + // Remove the prototype's type because we pass that separately. + rest.erase(prototype.type()); + return match_types(prototype.type(), + TypeVector(rest.begin(), rest.end()), + data.type(), + pfx); + } + // UUID, Date and URI match themselves or String. + if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) + { + static LLSD::Type accept[] = + { + LLSD::TypeString + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // We don't yet know the conversion semantics associated with any new LLSD + // data type that might be added, so until we've been extended to handle + // them, assume it's strict: the new type matches only itself. (This is + // true of Binary, which is why we don't handle that case separately.) Too + // bad LLSD doesn't define isConvertible(Type to, Type from). + return match_types(prototype.type(), TypeVector(), data.type(), pfx); +} + +bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits) +{ + LL_PROFILE_ZONE_SCOPED + + // We're comparing strict equality of LLSD representation rather than + // performing any conversions. So if the types aren't equal, the LLSD + // values aren't equal. + if (lhs.type() != rhs.type()) + { + return false; + } + + // Here we know both types are equal. Now compare values. + switch (lhs.type()) + { + case LLSD::TypeUndefined: + // Both are TypeUndefined. There's nothing more to know. + return true; + + case LLSD::TypeReal: + // This is where the 'bits' argument comes in handy. If passed + // explicitly, it means to use is_approx_equal_fraction() to compare. + if (bits >= 0) + { + return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); + } + // Otherwise we compare bit representations, and the usual caveats + // about comparing floating-point numbers apply. Omitting 'bits' when + // comparing Real values is only useful when we expect identical bit + // representation for a given Real value, e.g. for integer-valued + // Reals. + return (lhs.asReal() == rhs.asReal()); + +#define COMPARE_SCALAR(type) \ + case LLSD::Type##type: \ + /* LLSD::URI has operator!=() but not operator==() */ \ + /* rely on the optimizer for all others */ \ + return (! (lhs.as##type() != rhs.as##type())) + + COMPARE_SCALAR(Boolean); + COMPARE_SCALAR(Integer); + COMPARE_SCALAR(String); + COMPARE_SCALAR(UUID); + COMPARE_SCALAR(Date); + COMPARE_SCALAR(URI); + COMPARE_SCALAR(Binary); + +#undef COMPARE_SCALAR + + case LLSD::TypeArray: + { + LLSD::array_const_iterator + lai(lhs.beginArray()), laend(lhs.endArray()), + rai(rhs.beginArray()), raend(rhs.endArray()); + // Compare array elements, walking the two arrays in parallel. + for ( ; lai != laend && rai != raend; ++lai, ++rai) + { + // If any one array element is unequal, the arrays are unequal. + if (! llsd_equals(*lai, *rai, bits)) + return false; + } + // Here we've reached the end of one or the other array. They're equal + // only if they're BOTH at end: that is, if they have equal length too. + return (lai == laend && rai == raend); + } + + case LLSD::TypeMap: + { + // Build a set of all rhs keys. + std::set rhskeys; + for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); + rmi != rmend; ++rmi) + { + rhskeys.insert(rmi->first); + } + // Now walk all the lhs keys. + for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); + lmi != lmend; ++lmi) + { + // Try to erase this lhs key from the set of rhs keys. If rhs has + // no such key, the maps are unequal. erase(key) returns count of + // items erased. + if (rhskeys.erase(lmi->first) != 1) + return false; + // Both maps have the current key. Compare values. + if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) + return false; + } + // We've now established that all the lhs keys have equal values in + // both maps. The maps are equal unless rhs contains a superset of + // those keys. + return rhskeys.empty(); + } + + default: + // We expect that every possible type() value is specifically handled + // above. Failing to extend this switch to support a new LLSD type is + // an error that must be brought to the coder's attention. + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " + "unknown type " << lhs.type() << LL_ENDL; + return false; // pacify the compiler + } +} + +/***************************************************************************** +* llsd::drill() +*****************************************************************************/ +namespace llsd +{ + +LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) +{ + LL_PROFILE_ZONE_SCOPED + + // Treat rawPath uniformly as an array. If it's not already an array, + // store it as the only entry in one. (But let's say Undefined means an + // empty array.) + LLSD path; + if (rawPath.isArray() || rawPath.isUndefined()) + { + path = rawPath; + } + else + { + path.append(rawPath); + } + + // Need to indicate a current destination -- but that current destination + // must change as we step through the path array. Where normally we'd use + // an LLSD& to capture a subscripted LLSD lvalue, this time we must + // instead use a pointer -- since it must be reassigned. + // Start by pointing to the input blob exactly as is. + LLSD* located{&blob}; + + // Extract the element of interest by walking path. Use an explicit index + // so that, in case of a bogus type in path, we can identify the specific + // path entry that's bad. + for (LLSD::Integer i = 0; i < path.size(); ++i) + { + LL_PROFILE_ZONE_NUM( i ) + + const LLSD& key{path[i]}; + if (key.isString()) + { + // a string path element is a map key + located = &((*located)[key.asString()]); + } + else if (key.isInteger()) + { + // an integer path element is an array index + located = &((*located)[key.asInteger()]); + } + else + { + // What do we do with Real or Array or Map or ...? + // As it's a coder error -- not a user error -- rub the coder's + // face in it so it gets fixed. + LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath + << "): path[" << i << "] bad type " + << sTypes.lookup(key.type()) << LL_ENDL; + } + } + + // dereference the pointer to return a reference to the element we found + return *located; +} + +LLSD drill(const LLSD& blob, const LLSD& path) +{ + LL_PROFILE_ZONE_SCOPED + + // drill_ref() does exactly what we want. Temporarily cast away + // const-ness and use that. + return drill_ref(const_cast(blob), path); +} + +} // namespace llsd + +// Construct a deep partial clone of of an LLSD object. primitive types share +// references, however maps, arrays and binary objects are duplicated. An optional +// filter may be include to exclude/include keys in a map. +LLSD llsd_clone(LLSD value, LLSD filter) +{ + LL_PROFILE_ZONE_SCOPED + + LLSD clone; + bool has_filter(filter.isMap()); + + switch (value.type()) + { + case LLSD::TypeMap: + clone = LLSD::emptyMap(); + for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) + { + if (has_filter) + { + if (filter.has((*itm).first)) + { + if (!filter[(*itm).first].asBoolean()) + continue; + } + else if (filter.has("*")) + { + if (!filter["*"].asBoolean()) + continue; + } + else + { + continue; + } + } + clone[(*itm).first] = llsd_clone((*itm).second, filter); + } + break; + case LLSD::TypeArray: + clone = LLSD::emptyArray(); + for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) + { + clone.append(llsd_clone(*ita, filter)); + } + break; + + case LLSD::TypeBinary: + { + LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end()); + clone = LLSD::Binary(bin); + break; + } + default: + clone = value; + } + + return clone; +} + +LLSD llsd_shallow(LLSD value, LLSD filter) +{ + LLSD shallow; + bool has_filter(filter.isMap()); + + if (value.isMap()) + { + shallow = LLSD::emptyMap(); + for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) + { + if (has_filter) + { + if (filter.has((*itm).first)) + { + if (!filter[(*itm).first].asBoolean()) + continue; + } + else if (filter.has("*")) + { + if (!filter["*"].asBoolean()) + continue; + } + else + { + continue; + } + } + shallow[(*itm).first] = (*itm).second; + } + } + else if (value.isArray()) + { + shallow = LLSD::emptyArray(); + for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) + { + shallow.append(*ita); + } + } + else + { + return value; + } + + return shallow; +} + +LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) +{ + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + if (args.isMap()) + { + LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); + } + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + if (args.size() != arity) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", + args.size(), "-entry LLSD array)"))); + } + return args; + } + + // args is one of the scalar types + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + if (arity != 1) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " + "LLSD ", LLSD::typeString(args.type()), ")"))); + } + // make an array of it + return llsd::array(args); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index aa234e2f62..38bbe19ddd 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -1,674 +1,674 @@ -/** - * @file llsdutil.h - * @author Phoenix - * @date 2006-05-24 - * @brief Utility classes, functions, etc, for using structured data. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSDUTIL_H -#define LL_LLSDUTIL_H - -#include "apply.h" // LL::invoke() -#include "function_types.h" // LL::function_arity -#include "llsd.h" -#include -#include -#include // std::shared_ptr -#include -#include - -// U32 -LL_COMMON_API LLSD ll_sd_from_U32(const U32); -LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); - -// U64 -LL_COMMON_API LLSD ll_sd_from_U64(const U64); -LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); - -// IP Address -LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); -LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); - -// Binary to string -LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); - -//String to binary -LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); - -// Serializes sd to static buffer and returns pointer, useful for gdb debugging. -LL_COMMON_API char* ll_print_sd(const LLSD& sd); - -// Serializes sd to static buffer and returns pointer, using "pretty printing" mode. -LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); -LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); - -LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd); - -//compares the structure of an LLSD to a template LLSD and stores the -//"valid" values in a 3rd LLSD. Default values -//are pulled from the template. Extra keys/values in the test -//are ignored in the resultant LLSD. Ordering of arrays matters -//Returns false if the test is of same type but values differ in type -//Otherwise, returns true - -LL_COMMON_API bool compare_llsd_with_template( - const LLSD& llsd_to_test, - const LLSD& template_llsd, - LLSD& resultant_llsd); - -// filter_llsd_with_template() is a direct clone (copy-n-paste) of -// compare_llsd_with_template with the following differences: -// (1) bool vs BOOL return types -// (2) A map with the key value "*" is a special value and maps any key in the -// test llsd that doesn't have an explicitly matching key in the template. -// (3) The element of an array with exactly one element is taken as a template -// for *all* the elements of the test array. If the template array is of -// different size, compare_llsd_with_template() semantics apply. -bool filter_llsd_with_template( - const LLSD & llsd_to_test, - const LLSD & template_llsd, - LLSD & resultant_llsd); - -/** - * Recursively determine whether a given LLSD data block "matches" another - * LLSD prototype. The returned string is empty() on success, non-empty() on - * mismatch. - * - * This function tests structure (types) rather than data values. It is - * intended for when a consumer expects an LLSD block with a particular - * structure, and must succinctly detect whether the arriving block is - * well-formed. For instance, a test of the form: - * @code - * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) - * @endcode - * could instead be expressed by initializing a prototype LLSD map with the - * required keys and writing: - * @code - * if (! llsd_matches(prototype, data).empty()) - * @endcode - * - * A non-empty return value is an error-message fragment intended to indicate - * to (English-speaking) developers where in the prototype structure the - * mismatch occurred. - * - * * If a slot in the prototype isUndefined(), then anything is valid at that - * place in the real object. (Passing prototype == LLSD() matches anything - * at all.) - * * An array in the prototype must match a data array at least that large. - * (Additional entries in the data array are ignored.) Every isDefined() - * entry in the prototype array must match the corresponding entry in the - * data array. - * * A map in the prototype must match a map in the data. Every key in the - * prototype map must match a corresponding key in the data map. (Additional - * keys in the data map are ignored.) Every isDefined() value in the - * prototype map must match the corresponding key's value in the data map. - * * Scalar values in the prototype are tested for @em type rather than value. - * For instance, a String in the prototype matches any String at all. In - * effect, storing an Integer at a particular place in the prototype asserts - * that the caller intends to apply asInteger() to the corresponding slot in - * the data. - * * A String in the prototype matches String, Boolean, Integer, Real, UUID, - * Date and URI, because asString() applied to any of these produces a - * meaningful result. - * * Similarly, a Boolean, Integer or Real in the prototype can match any of - * Boolean, Integer or Real in the data -- or even String. - * * UUID matches UUID or String. - * * Date matches Date or String. - * * URI matches URI or String. - * * Binary in the prototype matches only Binary in the data. - * - * @TODO: when a Boolean, Integer or Real in the prototype matches a String in - * the data, we should examine the String @em value to ensure it can be - * meaningfully converted to the requested type. The same goes for UUID, Date - * and URI. - */ -LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); - -/// Deep equality. If you want to compare LLSD::Real values for approximate -/// equality rather than bitwise equality, pass @a bits as for -/// is_approx_equal_fraction(). -LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1); -/// If you don't care about LLSD::Real equality -inline bool operator==(const LLSD& lhs, const LLSD& rhs) -{ - return llsd_equals(lhs, rhs); -} -inline bool operator!=(const LLSD& lhs, const LLSD& rhs) -{ - // operator!=() should always be the negation of operator==() - return ! (lhs == rhs); -} - -// Simple function to copy data out of input & output iterators if -// there is no need for casting. -template LLSD llsd_copy_array(Input iter, Input end) -{ - LLSD dest; - for (; iter != end; ++iter) - { - dest.append(*iter); - } - return dest; -} - -namespace llsd -{ - -/** - * Drill down to locate an element in 'blob' according to 'path', where 'path' - * is one of the following: - * - * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'. - * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'. - * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a - * nested structure. For each element of 'path': - * - If it's an LLSD::Integer, select the entry with that index from an - * LLSD::Array at that level. - * - If it's an LLSD::String, select the entry with that key from an - * LLSD::Map at that level. - * - Anything else is an error. - * - * By implication, if path.isUndefined() or otherwise equivalent to an empty - * LLSD::Array, drill[_ref]() returns 'blob' as is. - */ -LLSD drill(const LLSD& blob, const LLSD& path); -LLSD& drill_ref( LLSD& blob, const LLSD& path); - -} - -namespace llsd -{ - -/** - * Construct an LLSD::Array inline, using modern C++ variadic arguments. - */ - -// recursion tail -inline -void array_(LLSD&) {} - -// recursive call -template -void array_(LLSD& data, T0&& v0, Ts&&... vs) -{ - data.append(std::forward(v0)); - array_(data, std::forward(vs)...); -} - -// public interface -template -LLSD array(Ts&&... vs) -{ - LLSD data; - array_(data, std::forward(vs)...); - return data; -} - -} // namespace llsd - -/***************************************************************************** -* LLSDMap -*****************************************************************************/ -/** - * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: - * - * @code - * void somefunc(const LLSD&); - * ... - * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); - * @endcode - * - * For completeness, LLSDMap() with no args constructs an empty map, so - * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) produces a map - * equivalent to the above. But for most purposes, LLSD() is already - * equivalent to an empty map, and if you explicitly want an empty isMap(), - * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() - * constructor follows the principle of least astonishment. - */ -class LLSDMap -{ -public: - LLSDMap(): - _data(LLSD::emptyMap()) - {} - LLSDMap(const LLSD::String& key, const LLSD& value): - _data(LLSD::emptyMap()) - { - _data[key] = value; - } - - LLSDMap& operator()(const LLSD::String& key, const LLSD& value) - { - _data[key] = value; - return *this; - } - - operator LLSD() const { return _data; } - LLSD get() const { return _data; } - -private: - LLSD _data; -}; - -namespace llsd -{ - -/** - * Construct an LLSD::Map inline, using modern C++ variadic arguments. - */ - -// recursion tail -inline -void map_(LLSD&) {} - -// recursive call -template -void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) -{ - data[k0] = v0; - map_(data, std::forward(vs)...); -} - -// public interface -template -LLSD map(Ts&&... vs) -{ - LLSD data; - map_(data, std::forward(vs)...); - return data; -} - -} // namespace llsd - -/***************************************************************************** -* LLSDParam -*****************************************************************************/ -struct LLSDParamBase -{ - virtual ~LLSDParamBase() {} -}; - -/** - * LLSDParam is a customization point for passing LLSD values to function - * parameters of more or less arbitrary type. LLSD provides a small set of - * native conversions; but if a generic algorithm explicitly constructs an - * LLSDParam object in the function's argument list, a consumer can provide - * LLSDParam specializations to support more different parameter types than - * LLSD's native conversions. - * - * Usage: - * - * @code - * void somefunc(const paramtype&); - * ... - * somefunc(..., LLSDParam(someLLSD), ...); - * @endcode - */ -template -class LLSDParam: public LLSDParamBase -{ -public: - /** - * Default implementation converts to T on construction, saves converted - * value for later retrieval - */ - LLSDParam(const LLSD& value): - value_(value) - {} - - operator T() const { return value_; } - -private: - T value_; -}; - -/** - * LLSDParam is for when you don't already have the target parameter - * type in hand. Instantiate LLSDParam(your LLSD object), and the - * templated conversion operator will try to select a more specific LLSDParam - * specialization. - */ -template <> -class LLSDParam: public LLSDParamBase -{ -private: - LLSD value_; - // LLSDParam::operator T() works by instantiating an LLSDParam on - // demand. Returning that engages LLSDParam::operator T(), producing - // the desired result. But LLSDParam owns a std::string whose - // c_str() is returned by its operator const char*(). If we return a temp - // LLSDParam, the compiler can destroy it right away, as soon - // as we've called operator const char*(). That's a problem! That - // invalidates the const char* we've just passed to the subject function. - // This LLSDParam is presumably guaranteed to survive until the - // subject function has returned, so we must ensure that any constructed - // LLSDParam lives just as long as this LLSDParam does. Putting - // each LLSDParam on the heap and capturing a smart pointer in a vector - // works. We would have liked to use std::unique_ptr, but vector entries - // must be copyable. - // (Alternatively we could assume that every instance of LLSDParam - // will be asked for at most ONE conversion. We could store a scalar - // std::unique_ptr and, when constructing an new LLSDParam, assert that - // the unique_ptr is empty. But some future change in usage patterns, and - // consequent failure of that assertion, would be very mysterious. Instead - // of explaining how to fix it, just fix it now.) - mutable std::vector> converters_; - -public: - LLSDParam(const LLSD& value): value_(value) {} - - /// if we're literally being asked for an LLSD parameter, avoid infinite - /// recursion - operator LLSD() const { return value_; } - - /// otherwise, instantiate a more specific LLSDParam to convert; that - /// preserves the existing customization mechanism - template - operator T() const - { - // capture 'ptr' with the specific subclass type because converters_ - // only stores LLSDParamBase pointers - auto ptr{ std::make_shared>>(value_) }; - // keep the new converter alive until we ourselves are destroyed - converters_.push_back(ptr); - return *ptr; - } -}; - -/** - * Turns out that several target types could accept an LLSD param using any of - * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or - * std::string. Therefore, the compiler can't decide which LLSD conversion - * operator to choose, even though to us it seems obvious. But that's okay, we - * can specialize LLSDParam for such target types, explicitly specifying the - * desired conversion -- that's part of what LLSDParam is all about. Turns out - * we have to do that enough to make it worthwhile generalizing. Use a macro - * because I need to specify one of the asReal, etc., explicit conversion - * methods as well as a type. If I'm overlooking a clever way to implement - * that using a template instead, feel free to reimplement. - */ -#define LLSDParam_for(T, AS) \ -template <> \ -class LLSDParam: public LLSDParamBase \ -{ \ -public: \ - LLSDParam(const LLSD& value): \ - value_((T)value.AS()) \ - {} \ - \ - operator T() const { return value_; } \ - \ -private: \ - T value_; \ -} - -LLSDParam_for(float, asReal); -LLSDParam_for(LLUUID, asUUID); -LLSDParam_for(LLDate, asDate); -LLSDParam_for(LLURI, asURI); -LLSDParam_for(LLSD::Binary, asBinary); - -/** - * LLSDParam is an example of the kind of conversion you can - * support with LLSDParam beyond native LLSD conversions. Normally you can't - * pass an LLSD object to a function accepting const char* -- but you can - * safely pass an LLSDParam(yourLLSD). - */ -template <> -class LLSDParam: public LLSDParamBase -{ -private: - // The difference here is that we store a std::string rather than a const - // char*. It's important that the LLSDParam object own the std::string. - std::string value_; - // We don't bother storing the incoming LLSD object, but we do have to - // distinguish whether value_ is an empty string because the LLSD object - // contains an empty string or because it's isUndefined(). - bool undefined_; - -public: - LLSDParam(const LLSD& value): - value_(value), - undefined_(value.isUndefined()) - {} - - // The const char* we retrieve is for storage owned by our value_ member. - // That's how we guarantee that the const char* is valid for the lifetime - // of this LLSDParam object. Constructing your LLSDParam in the argument - // list should ensure that the LLSDParam object will persist for the - // duration of the function call. - operator const char*() const - { - if (undefined_) - { - // By default, an isUndefined() LLSD object's asString() method - // will produce an empty string. But for a function accepting - // const char*, it's often important to be able to pass NULL, and - // isUndefined() seems like the best way. If you want to pass an - // empty string, you can still pass LLSD(""). Without this special - // case, though, no LLSD value could pass NULL. - return NULL; - } - return value_.c_str(); - } -}; - -namespace llsd -{ - -/***************************************************************************** -* range-based for-loop helpers for LLSD -*****************************************************************************/ -/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } -class inArray -{ -public: - inArray(const LLSD& array): - _array(array) - {} - - typedef LLSD::array_const_iterator const_iterator; - typedef LLSD::array_iterator iterator; - - iterator begin() { return _array.beginArray(); } - iterator end() { return _array.endArray(); } - const_iterator begin() const { return _array.beginArray(); } - const_iterator end() const { return _array.endArray(); } - -private: - LLSD _array; -}; - -/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. -typedef std::map::value_type MapEntry; - -/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... } -class inMap -{ -public: - inMap(const LLSD& map): - _map(map) - {} - - typedef LLSD::map_const_iterator const_iterator; - typedef LLSD::map_iterator iterator; - - iterator begin() { return _map.beginMap(); } - iterator end() { return _map.endMap(); } - const_iterator begin() const { return _map.beginMap(); } - const_iterator end() const { return _map.endMap(); } - -private: - LLSD _map; -}; - -} // namespace llsd - - -// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects -// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply -// use a shared reference. -// Optionally a filter may be specified to control what is duplicated. The -// map takes the form "keyname/boolean". -// If the value is true the value will be duplicated otherwise it will be skipped -// when encountered in a map. A key name of "*" can be specified as a wild card -// and will specify the default behavior. If no wild card is given and the clone -// encounters a name not in the filter, that value will be skipped. -LLSD llsd_clone(LLSD value, LLSD filter = LLSD()); - -// Creates a shallow copy of a map or array. If passed any other type of LLSD -// object it simply returns that value. See llsd_clone for a description of -// the filter parameter. -LLSD llsd_shallow(LLSD value, LLSD filter = LLSD()); - -namespace llsd -{ - -// llsd namespace aliases -inline -LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); } -inline -LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); } - -} // namespace llsd - -// Specialization for generating a hash value from an LLSD block. -namespace boost -{ -template <> -struct hash -{ - typedef LLSD argument_type; - typedef std::size_t result_type; - result_type operator()(argument_type const& s) const - { - result_type seed(0); - - LLSD::Type stype = s.type(); - boost::hash_combine(seed, (S32)stype); - - switch (stype) - { - case LLSD::TypeBoolean: - boost::hash_combine(seed, s.asBoolean()); - break; - case LLSD::TypeInteger: - boost::hash_combine(seed, s.asInteger()); - break; - case LLSD::TypeReal: - boost::hash_combine(seed, s.asReal()); - break; - case LLSD::TypeURI: - case LLSD::TypeString: - boost::hash_combine(seed, s.asString()); - break; - case LLSD::TypeUUID: - boost::hash_combine(seed, s.asUUID()); - break; - case LLSD::TypeDate: - boost::hash_combine(seed, s.asDate().secondsSinceEpoch()); - break; - case LLSD::TypeBinary: - { - const LLSD::Binary &b(s.asBinary()); - boost::hash_range(seed, b.begin(), b.end()); - break; - } - case LLSD::TypeMap: - { - for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm) - { - boost::hash_combine(seed, (*itm).first); - boost::hash_combine(seed, (*itm).second); - } - break; - } - case LLSD::TypeArray: - for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita) - { - boost::hash_combine(seed, (*ita)); - } - break; - case LLSD::TypeUndefined: - default: - break; - } - - return seed; - } -}; -} - -namespace LL -{ - -/***************************************************************************** -* apply(function, LLSD array) -*****************************************************************************/ -// validate incoming LLSD blob, and return an LLSD array suitable to pass to -// the function of interest -LLSD apply_llsd_fix(size_t arity, const LLSD& args); - -// Derived from https://stackoverflow.com/a/20441189 -// and https://en.cppreference.com/w/cpp/utility/apply . -// We can't simply make a tuple from the LLSD array and then apply() that -// tuple to the function -- how would make_tuple() deduce the correct -// parameter type for each entry? We must go directly to the target function. -template -auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) -{ - // call func(unpacked args), using generic LLSDParam to convert each - // entry in 'array' to the target parameter type - return std::forward(func)(LLSDParam(array[I])...); -} - -// use apply_n(function, LLSD) to call a specific arity of a variadic -// function with (that many) items from the passed LLSD array -template -auto apply_n(CALLABLE&& func, const LLSD& args) -{ - return apply_impl(std::forward(func), - apply_llsd_fix(ARITY, args), - std::make_index_sequence()); -} - -/** - * apply(function, LLSD) goes beyond C++17 std::apply(). For this case - * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. (But see apply_n() to - * pass a specific number of args to a variadic function.) - */ -template -auto apply(CALLABLE&& func, const LLSD& args) -{ - // infer arity from the definition of func - constexpr auto arity = function_arity< - typename std::remove_reference::type>::value; - // now that we have a compile-time arity, apply_n() works - return apply_n(std::forward(func), args); -} - -} // namespace LL - -#endif // LL_LLSDUTIL_H +/** + * @file llsdutil.h + * @author Phoenix + * @date 2006-05-24 + * @brief Utility classes, functions, etc, for using structured data. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSDUTIL_H +#define LL_LLSDUTIL_H + +#include "apply.h" // LL::invoke() +#include "function_types.h" // LL::function_arity +#include "llsd.h" +#include +#include +#include // std::shared_ptr +#include +#include + +// U32 +LL_COMMON_API LLSD ll_sd_from_U32(const U32); +LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); + +// U64 +LL_COMMON_API LLSD ll_sd_from_U64(const U64); +LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); + +// IP Address +LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); +LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); + +// Binary to string +LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); + +//String to binary +LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); + +// Serializes sd to static buffer and returns pointer, useful for gdb debugging. +LL_COMMON_API char* ll_print_sd(const LLSD& sd); + +// Serializes sd to static buffer and returns pointer, using "pretty printing" mode. +LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); +LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); + +LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd); + +//compares the structure of an LLSD to a template LLSD and stores the +//"valid" values in a 3rd LLSD. Default values +//are pulled from the template. Extra keys/values in the test +//are ignored in the resultant LLSD. Ordering of arrays matters +//Returns false if the test is of same type but values differ in type +//Otherwise, returns true + +LL_COMMON_API bool compare_llsd_with_template( + const LLSD& llsd_to_test, + const LLSD& template_llsd, + LLSD& resultant_llsd); + +// filter_llsd_with_template() is a direct clone (copy-n-paste) of +// compare_llsd_with_template with the following differences: +// (1) bool vs BOOL return types +// (2) A map with the key value "*" is a special value and maps any key in the +// test llsd that doesn't have an explicitly matching key in the template. +// (3) The element of an array with exactly one element is taken as a template +// for *all* the elements of the test array. If the template array is of +// different size, compare_llsd_with_template() semantics apply. +bool filter_llsd_with_template( + const LLSD & llsd_to_test, + const LLSD & template_llsd, + LLSD & resultant_llsd); + +/** + * Recursively determine whether a given LLSD data block "matches" another + * LLSD prototype. The returned string is empty() on success, non-empty() on + * mismatch. + * + * This function tests structure (types) rather than data values. It is + * intended for when a consumer expects an LLSD block with a particular + * structure, and must succinctly detect whether the arriving block is + * well-formed. For instance, a test of the form: + * @code + * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) + * @endcode + * could instead be expressed by initializing a prototype LLSD map with the + * required keys and writing: + * @code + * if (! llsd_matches(prototype, data).empty()) + * @endcode + * + * A non-empty return value is an error-message fragment intended to indicate + * to (English-speaking) developers where in the prototype structure the + * mismatch occurred. + * + * * If a slot in the prototype isUndefined(), then anything is valid at that + * place in the real object. (Passing prototype == LLSD() matches anything + * at all.) + * * An array in the prototype must match a data array at least that large. + * (Additional entries in the data array are ignored.) Every isDefined() + * entry in the prototype array must match the corresponding entry in the + * data array. + * * A map in the prototype must match a map in the data. Every key in the + * prototype map must match a corresponding key in the data map. (Additional + * keys in the data map are ignored.) Every isDefined() value in the + * prototype map must match the corresponding key's value in the data map. + * * Scalar values in the prototype are tested for @em type rather than value. + * For instance, a String in the prototype matches any String at all. In + * effect, storing an Integer at a particular place in the prototype asserts + * that the caller intends to apply asInteger() to the corresponding slot in + * the data. + * * A String in the prototype matches String, Boolean, Integer, Real, UUID, + * Date and URI, because asString() applied to any of these produces a + * meaningful result. + * * Similarly, a Boolean, Integer or Real in the prototype can match any of + * Boolean, Integer or Real in the data -- or even String. + * * UUID matches UUID or String. + * * Date matches Date or String. + * * URI matches URI or String. + * * Binary in the prototype matches only Binary in the data. + * + * @TODO: when a Boolean, Integer or Real in the prototype matches a String in + * the data, we should examine the String @em value to ensure it can be + * meaningfully converted to the requested type. The same goes for UUID, Date + * and URI. + */ +LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); + +/// Deep equality. If you want to compare LLSD::Real values for approximate +/// equality rather than bitwise equality, pass @a bits as for +/// is_approx_equal_fraction(). +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1); +/// If you don't care about LLSD::Real equality +inline bool operator==(const LLSD& lhs, const LLSD& rhs) +{ + return llsd_equals(lhs, rhs); +} +inline bool operator!=(const LLSD& lhs, const LLSD& rhs) +{ + // operator!=() should always be the negation of operator==() + return ! (lhs == rhs); +} + +// Simple function to copy data out of input & output iterators if +// there is no need for casting. +template LLSD llsd_copy_array(Input iter, Input end) +{ + LLSD dest; + for (; iter != end; ++iter) + { + dest.append(*iter); + } + return dest; +} + +namespace llsd +{ + +/** + * Drill down to locate an element in 'blob' according to 'path', where 'path' + * is one of the following: + * + * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'. + * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'. + * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a + * nested structure. For each element of 'path': + * - If it's an LLSD::Integer, select the entry with that index from an + * LLSD::Array at that level. + * - If it's an LLSD::String, select the entry with that key from an + * LLSD::Map at that level. + * - Anything else is an error. + * + * By implication, if path.isUndefined() or otherwise equivalent to an empty + * LLSD::Array, drill[_ref]() returns 'blob' as is. + */ +LLSD drill(const LLSD& blob, const LLSD& path); +LLSD& drill_ref( LLSD& blob, const LLSD& path); + +} + +namespace llsd +{ + +/** + * Construct an LLSD::Array inline, using modern C++ variadic arguments. + */ + +// recursion tail +inline +void array_(LLSD&) {} + +// recursive call +template +void array_(LLSD& data, T0&& v0, Ts&&... vs) +{ + data.append(std::forward(v0)); + array_(data, std::forward(vs)...); +} + +// public interface +template +LLSD array(Ts&&... vs) +{ + LLSD data; + array_(data, std::forward(vs)...); + return data; +} + +} // namespace llsd + +/***************************************************************************** +* LLSDMap +*****************************************************************************/ +/** + * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); + * @endcode + * + * For completeness, LLSDMap() with no args constructs an empty map, so + * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) produces a map + * equivalent to the above. But for most purposes, LLSD() is already + * equivalent to an empty map, and if you explicitly want an empty isMap(), + * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() + * constructor follows the principle of least astonishment. + */ +class LLSDMap +{ +public: + LLSDMap(): + _data(LLSD::emptyMap()) + {} + LLSDMap(const LLSD::String& key, const LLSD& value): + _data(LLSD::emptyMap()) + { + _data[key] = value; + } + + LLSDMap& operator()(const LLSD::String& key, const LLSD& value) + { + _data[key] = value; + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +namespace llsd +{ + +/** + * Construct an LLSD::Map inline, using modern C++ variadic arguments. + */ + +// recursion tail +inline +void map_(LLSD&) {} + +// recursive call +template +void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) +{ + data[k0] = v0; + map_(data, std::forward(vs)...); +} + +// public interface +template +LLSD map(Ts&&... vs) +{ + LLSD data; + map_(data, std::forward(vs)...); + return data; +} + +} // namespace llsd + +/***************************************************************************** +* LLSDParam +*****************************************************************************/ +struct LLSDParamBase +{ + virtual ~LLSDParamBase() {} +}; + +/** + * LLSDParam is a customization point for passing LLSD values to function + * parameters of more or less arbitrary type. LLSD provides a small set of + * native conversions; but if a generic algorithm explicitly constructs an + * LLSDParam object in the function's argument list, a consumer can provide + * LLSDParam specializations to support more different parameter types than + * LLSD's native conversions. + * + * Usage: + * + * @code + * void somefunc(const paramtype&); + * ... + * somefunc(..., LLSDParam(someLLSD), ...); + * @endcode + */ +template +class LLSDParam: public LLSDParamBase +{ +public: + /** + * Default implementation converts to T on construction, saves converted + * value for later retrieval + */ + LLSDParam(const LLSD& value): + value_(value) + {} + + operator T() const { return value_; } + +private: + T value_; +}; + +/** + * LLSDParam is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam: public LLSDParamBase +{ +private: + LLSD value_; + // LLSDParam::operator T() works by instantiating an LLSDParam on + // demand. Returning that engages LLSDParam::operator T(), producing + // the desired result. But LLSDParam owns a std::string whose + // c_str() is returned by its operator const char*(). If we return a temp + // LLSDParam, the compiler can destroy it right away, as soon + // as we've called operator const char*(). That's a problem! That + // invalidates the const char* we've just passed to the subject function. + // This LLSDParam is presumably guaranteed to survive until the + // subject function has returned, so we must ensure that any constructed + // LLSDParam lives just as long as this LLSDParam does. Putting + // each LLSDParam on the heap and capturing a smart pointer in a vector + // works. We would have liked to use std::unique_ptr, but vector entries + // must be copyable. + // (Alternatively we could assume that every instance of LLSDParam + // will be asked for at most ONE conversion. We could store a scalar + // std::unique_ptr and, when constructing an new LLSDParam, assert that + // the unique_ptr is empty. But some future change in usage patterns, and + // consequent failure of that assertion, would be very mysterious. Instead + // of explaining how to fix it, just fix it now.) + mutable std::vector> converters_; + +public: + LLSDParam(const LLSD& value): value_(value) {} + + /// if we're literally being asked for an LLSD parameter, avoid infinite + /// recursion + operator LLSD() const { return value_; } + + /// otherwise, instantiate a more specific LLSDParam to convert; that + /// preserves the existing customization mechanism + template + operator T() const + { + // capture 'ptr' with the specific subclass type because converters_ + // only stores LLSDParamBase pointers + auto ptr{ std::make_shared>>(value_) }; + // keep the new converter alive until we ourselves are destroyed + converters_.push_back(ptr); + return *ptr; + } +}; + +/** + * Turns out that several target types could accept an LLSD param using any of + * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or + * std::string. Therefore, the compiler can't decide which LLSD conversion + * operator to choose, even though to us it seems obvious. But that's okay, we + * can specialize LLSDParam for such target types, explicitly specifying the + * desired conversion -- that's part of what LLSDParam is all about. Turns out + * we have to do that enough to make it worthwhile generalizing. Use a macro + * because I need to specify one of the asReal, etc., explicit conversion + * methods as well as a type. If I'm overlooking a clever way to implement + * that using a template instead, feel free to reimplement. + */ +#define LLSDParam_for(T, AS) \ +template <> \ +class LLSDParam: public LLSDParamBase \ +{ \ +public: \ + LLSDParam(const LLSD& value): \ + value_((T)value.AS()) \ + {} \ + \ + operator T() const { return value_; } \ + \ +private: \ + T value_; \ +} + +LLSDParam_for(float, asReal); +LLSDParam_for(LLUUID, asUUID); +LLSDParam_for(LLDate, asDate); +LLSDParam_for(LLURI, asURI); +LLSDParam_for(LLSD::Binary, asBinary); + +/** + * LLSDParam is an example of the kind of conversion you can + * support with LLSDParam beyond native LLSD conversions. Normally you can't + * pass an LLSD object to a function accepting const char* -- but you can + * safely pass an LLSDParam(yourLLSD). + */ +template <> +class LLSDParam: public LLSDParamBase +{ +private: + // The difference here is that we store a std::string rather than a const + // char*. It's important that the LLSDParam object own the std::string. + std::string value_; + // We don't bother storing the incoming LLSD object, but we do have to + // distinguish whether value_ is an empty string because the LLSD object + // contains an empty string or because it's isUndefined(). + bool undefined_; + +public: + LLSDParam(const LLSD& value): + value_(value), + undefined_(value.isUndefined()) + {} + + // The const char* we retrieve is for storage owned by our value_ member. + // That's how we guarantee that the const char* is valid for the lifetime + // of this LLSDParam object. Constructing your LLSDParam in the argument + // list should ensure that the LLSDParam object will persist for the + // duration of the function call. + operator const char*() const + { + if (undefined_) + { + // By default, an isUndefined() LLSD object's asString() method + // will produce an empty string. But for a function accepting + // const char*, it's often important to be able to pass NULL, and + // isUndefined() seems like the best way. If you want to pass an + // empty string, you can still pass LLSD(""). Without this special + // case, though, no LLSD value could pass NULL. + return NULL; + } + return value_.c_str(); + } +}; + +namespace llsd +{ + +/***************************************************************************** +* range-based for-loop helpers for LLSD +*****************************************************************************/ +/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } +class inArray +{ +public: + inArray(const LLSD& array): + _array(array) + {} + + typedef LLSD::array_const_iterator const_iterator; + typedef LLSD::array_iterator iterator; + + iterator begin() { return _array.beginArray(); } + iterator end() { return _array.endArray(); } + const_iterator begin() const { return _array.beginArray(); } + const_iterator end() const { return _array.endArray(); } + +private: + LLSD _array; +}; + +/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. +typedef std::map::value_type MapEntry; + +/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... } +class inMap +{ +public: + inMap(const LLSD& map): + _map(map) + {} + + typedef LLSD::map_const_iterator const_iterator; + typedef LLSD::map_iterator iterator; + + iterator begin() { return _map.beginMap(); } + iterator end() { return _map.endMap(); } + const_iterator begin() const { return _map.beginMap(); } + const_iterator end() const { return _map.endMap(); } + +private: + LLSD _map; +}; + +} // namespace llsd + + +// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects +// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply +// use a shared reference. +// Optionally a filter may be specified to control what is duplicated. The +// map takes the form "keyname/boolean". +// If the value is true the value will be duplicated otherwise it will be skipped +// when encountered in a map. A key name of "*" can be specified as a wild card +// and will specify the default behavior. If no wild card is given and the clone +// encounters a name not in the filter, that value will be skipped. +LLSD llsd_clone(LLSD value, LLSD filter = LLSD()); + +// Creates a shallow copy of a map or array. If passed any other type of LLSD +// object it simply returns that value. See llsd_clone for a description of +// the filter parameter. +LLSD llsd_shallow(LLSD value, LLSD filter = LLSD()); + +namespace llsd +{ + +// llsd namespace aliases +inline +LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); } +inline +LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); } + +} // namespace llsd + +// Specialization for generating a hash value from an LLSD block. +namespace boost +{ +template <> +struct hash +{ + typedef LLSD argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& s) const + { + result_type seed(0); + + LLSD::Type stype = s.type(); + boost::hash_combine(seed, (S32)stype); + + switch (stype) + { + case LLSD::TypeBoolean: + boost::hash_combine(seed, s.asBoolean()); + break; + case LLSD::TypeInteger: + boost::hash_combine(seed, s.asInteger()); + break; + case LLSD::TypeReal: + boost::hash_combine(seed, s.asReal()); + break; + case LLSD::TypeURI: + case LLSD::TypeString: + boost::hash_combine(seed, s.asString()); + break; + case LLSD::TypeUUID: + boost::hash_combine(seed, s.asUUID()); + break; + case LLSD::TypeDate: + boost::hash_combine(seed, s.asDate().secondsSinceEpoch()); + break; + case LLSD::TypeBinary: + { + const LLSD::Binary &b(s.asBinary()); + boost::hash_range(seed, b.begin(), b.end()); + break; + } + case LLSD::TypeMap: + { + for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm) + { + boost::hash_combine(seed, (*itm).first); + boost::hash_combine(seed, (*itm).second); + } + break; + } + case LLSD::TypeArray: + for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita) + { + boost::hash_combine(seed, (*ita)); + } + break; + case LLSD::TypeUndefined: + default: + break; + } + + return seed; + } +}; +} + +namespace LL +{ + +/***************************************************************************** +* apply(function, LLSD array) +*****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// the function of interest +LLSD apply_llsd_fix(size_t arity, const LLSD& args); + +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply . +// We can't simply make a tuple from the LLSD array and then apply() that +// tuple to the function -- how would make_tuple() deduce the correct +// parameter type for each entry? We must go directly to the target function. +template +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) +{ + // call func(unpacked args), using generic LLSDParam to convert each + // entry in 'array' to the target parameter type + return std::forward(func)(LLSDParam(array[I])...); +} + +// use apply_n(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence()); +} + +/** + * apply(function, LLSD) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) + */ +template +auto apply(CALLABLE&& func, const LLSD& args) +{ + // infer arity from the definition of func + constexpr auto arity = function_arity< + typename std::remove_reference::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); +} + +} // namespace LL + +#endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 05e71b8203..bda3579f60 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -1,168 +1,168 @@ -/** - * @file llstacktrace.cpp - * @brief stack tracing functionality - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llstacktrace.h" - -#ifdef LL_WINDOWS - -#include -#include - -#include "llwin32headerslean.h" -#pragma warning (push) -#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. -#include -#pragma warning (pop) - -typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( - IN ULONG frames_to_skip, - IN ULONG frames_to_capture, - OUT PVOID *backtrace, - OUT PULONG backtrace_hash); - -static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = - (RtlCaptureStackBackTrace_Function*) - GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); - -bool ll_get_stack_trace(std::vector& lines) -{ - const S32 MAX_STACK_DEPTH = 32; - const S32 STRING_NAME_LENGTH = 200; - const S32 FRAME_SKIP = 2; - static bool symbolsLoaded = false; - static bool firstCall = true; - - HANDLE hProc = GetCurrentProcess(); - - // load the symbols if they're not loaded - if(!symbolsLoaded && firstCall) - { - symbolsLoaded = SymInitialize(hProc, NULL, true); - firstCall = false; - } - - // if loaded, get the call stack - if(symbolsLoaded) - { - // create the frames to hold the addresses - void* frames[MAX_STACK_DEPTH]; - memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH); - S32 depth = 0; - - // get the addresses - depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL); - - IMAGEHLP_LINE64 line; - memset(&line, 0, sizeof(IMAGEHLP_LINE64)); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - // create something to hold address info - PIMAGEHLP_SYMBOL64 pSym; - pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); - memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); - pSym->MaxNameLength = STRING_NAME_LENGTH; - pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - - // get address info for each address frame - // and store - for(S32 i=0; i < depth; i++) - { - std::stringstream stack_line; - bool ret; - - DWORD64 addr = (DWORD64)frames[i]; - ret = SymGetSymFromAddr64(hProc, addr, 0, pSym); - if(ret) - { - stack_line << pSym->Name << " "; - } - - DWORD dummy; - ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line); - if(ret) - { - std::string file_name = line.FileName; - std::string::size_type index = file_name.rfind("\\"); - stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber; - } - - lines.push_back(stack_line.str()); - } - - free(pSym); - - // TODO: figure out a way to cleanup symbol loading - // Not hugely necessary, however. - //SymCleanup(hProc); - return true; - } - else - { - lines.push_back("Stack Trace Failed. PDB symbol info not loaded"); - } - - return false; -} - -void ll_get_stack_trace_internal(std::vector& lines) -{ - const S32 MAX_STACK_DEPTH = 100; - const S32 STRING_NAME_LENGTH = 256; - - HANDLE process = GetCurrentProcess(); - SymInitialize( process, NULL, true ); - - void *stack[MAX_STACK_DEPTH]; - - unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL ); - SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1); - symbol->MaxNameLen = STRING_NAME_LENGTH-1; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - - for(unsigned int i = 0; i < frames; i++) - { - SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); - lines.push_back(symbol->Name); - } - - free( symbol ); -} - -#else - -bool ll_get_stack_trace(std::vector& lines) -{ - return false; -} - -void ll_get_stack_trace_internal(std::vector& lines) -{ - -} - -#endif - +/** + * @file llstacktrace.cpp + * @brief stack tracing functionality + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llstacktrace.h" + +#ifdef LL_WINDOWS + +#include +#include + +#include "llwin32headerslean.h" +#pragma warning (push) +#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. +#include +#pragma warning (pop) + +typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( + IN ULONG frames_to_skip, + IN ULONG frames_to_capture, + OUT PVOID *backtrace, + OUT PULONG backtrace_hash); + +static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = + (RtlCaptureStackBackTrace_Function*) + GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); + +bool ll_get_stack_trace(std::vector& lines) +{ + const S32 MAX_STACK_DEPTH = 32; + const S32 STRING_NAME_LENGTH = 200; + const S32 FRAME_SKIP = 2; + static bool symbolsLoaded = false; + static bool firstCall = true; + + HANDLE hProc = GetCurrentProcess(); + + // load the symbols if they're not loaded + if(!symbolsLoaded && firstCall) + { + symbolsLoaded = SymInitialize(hProc, NULL, true); + firstCall = false; + } + + // if loaded, get the call stack + if(symbolsLoaded) + { + // create the frames to hold the addresses + void* frames[MAX_STACK_DEPTH]; + memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH); + S32 depth = 0; + + // get the addresses + depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL); + + IMAGEHLP_LINE64 line; + memset(&line, 0, sizeof(IMAGEHLP_LINE64)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + // create something to hold address info + PIMAGEHLP_SYMBOL64 pSym; + pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); + memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); + pSym->MaxNameLength = STRING_NAME_LENGTH; + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + + // get address info for each address frame + // and store + for(S32 i=0; i < depth; i++) + { + std::stringstream stack_line; + bool ret; + + DWORD64 addr = (DWORD64)frames[i]; + ret = SymGetSymFromAddr64(hProc, addr, 0, pSym); + if(ret) + { + stack_line << pSym->Name << " "; + } + + DWORD dummy; + ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line); + if(ret) + { + std::string file_name = line.FileName; + std::string::size_type index = file_name.rfind("\\"); + stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber; + } + + lines.push_back(stack_line.str()); + } + + free(pSym); + + // TODO: figure out a way to cleanup symbol loading + // Not hugely necessary, however. + //SymCleanup(hProc); + return true; + } + else + { + lines.push_back("Stack Trace Failed. PDB symbol info not loaded"); + } + + return false; +} + +void ll_get_stack_trace_internal(std::vector& lines) +{ + const S32 MAX_STACK_DEPTH = 100; + const S32 STRING_NAME_LENGTH = 256; + + HANDLE process = GetCurrentProcess(); + SymInitialize( process, NULL, true ); + + void *stack[MAX_STACK_DEPTH]; + + unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL ); + SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1); + symbol->MaxNameLen = STRING_NAME_LENGTH-1; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + for(unsigned int i = 0; i < frames; i++) + { + SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); + lines.push_back(symbol->Name); + } + + free( symbol ); +} + +#else + +bool ll_get_stack_trace(std::vector& lines) +{ + return false; +} + +void ll_get_stack_trace_internal(std::vector& lines) +{ + +} + +#endif + diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index f2d268bb9a..67b4c141af 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -1,713 +1,713 @@ -/** - * @file llstl.h - * @brief helper object & functions for use with the stl. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSTL_H -#define LL_LLSTL_H - -#include "stdtypes.h" -#include -#include -#include -#include -#include -#include -#include - -#ifdef LL_LINUX -// For strcmp -#include -#endif -// Use to compare the first element only of a pair -// e.g. typedef std::set, compare_pair > some_pair_set_t; -template -struct compare_pair_first -{ - bool operator()(const std::pair& a, const std::pair& b) const - { - return a.first < b.first; - } -}; - -template -struct compare_pair_greater -{ - bool operator()(const std::pair& a, const std::pair& b) const - { - if (!(a.first < b.first)) - return true; - else if (!(b.first < a.first)) - return false; - else - return !(a.second < b.second); - } -}; - -// Use to compare the contents of two pointers (e.g. std::string*) -template -struct compare_pointer_contents -{ - typedef const T* Tptr; - bool operator()(const Tptr& a, const Tptr& b) const - { - return *a < *b; - } -}; - -// DeletePointer is a simple helper for deleting all pointers in a container. -// The general form is: -// -// std::for_each(cont.begin(), cont.end(), DeletePointer()); -// somemap.clear(); -// -// Don't forget to clear()! - -struct DeletePointer -{ - template void operator()(T* ptr) const - { - delete ptr; - } -}; -struct DeletePointerArray -{ - template void operator()(T* ptr) const - { - delete[] ptr; - } -}; - -// DeletePairedPointer is a simple helper for deleting all pointers in a map. -// The general form is: -// -// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer()); -// somemap.clear(); // Don't leave dangling pointers around - -struct DeletePairedPointer -{ - template void operator()(T &ptr) const - { - delete ptr.second; - ptr.second = NULL; - } -}; -struct DeletePairedPointerArray -{ - template void operator()(T &ptr) const - { - delete[] ptr.second; - ptr.second = NULL; - } -}; - - -// Alternate version of the above so that has a more cumbersome -// syntax, but it can be used with compositional functors. -// NOTE: The functor retuns a bool because msdev bombs during the -// composition if you return void. Once we upgrade to a newer -// compiler, the second unary_function template parameter can be set -// to void. -// -// Here's a snippet showing how you use this object: -// -// typedef std::map map_type; -// map_type widget_map; -// ... // add elements -// // delete them all -// for_each(widget_map.begin(), -// widget_map.end(), -// llcompose1(DeletePointerFunctor(), -// llselect2nd())); - -template -struct DeletePointerFunctor -{ - bool operator()(T* ptr) const - { - delete ptr; - return true; - } -}; - -// See notes about DeleteArray for why you should consider avoiding this. -template -struct DeleteArrayFunctor -{ - bool operator()(T* ptr) const - { - delete[] ptr; - return true; - } -}; - -// CopyNewPointer is a simple helper which accepts a pointer, and -// returns a new pointer built with the copy constructor. Example: -// -// transform(in.begin(), in.end(), out.end(), CopyNewPointer()); - -struct CopyNewPointer -{ - template T* operator()(const T* ptr) const - { - return new T(*ptr); - } -}; - -template -void delete_and_clear(std::list& list) -{ - std::for_each(list.begin(), list.end(), DeletePointer()); - list.clear(); -} - -template -void delete_and_clear(std::vector& vector) -{ - std::for_each(vector.begin(), vector.end(), DeletePointer()); - vector.clear(); -} - -template -void delete_and_clear(std::set& set) -{ - std::for_each(set.begin(), set.end(), DeletePointer()); - set.clear(); -} - -template -void delete_and_clear(std::map& map) -{ - std::for_each(map.begin(), map.end(), DeletePairedPointer()); - map.clear(); -} - -template -void delete_and_clear(T*& ptr) -{ - delete ptr; - ptr = NULL; -} - - -template -void delete_and_clear_array(T*& ptr) -{ - delete[] ptr; - ptr = NULL; -} - -// Simple function to help with finding pointers in maps. -// For example: -// typedef map_t; -// std::map foo; -// foo[18] = "there"; -// foo[2] = "hello"; -// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" -// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL -template -inline T* get_ptr_in_map(const std::map& inmap, const K& key) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map::const_iterator map_iter; - map_iter iter = inmap.find(key); - if(iter == inmap.end()) - { - return NULL; - } - else - { - return iter->second; - } -}; - -// helper function which returns true if key is in inmap. -template -inline bool is_in_map(const std::map& inmap, const K& key) -{ - if(inmap.find(key) == inmap.end()) - { - return false; - } - else - { - return true; - } -} - -// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor. -// To replace LLSkipMap getIfThere, use: -// get_if_there(map, key, 0) -// WARNING: Make sure default_value (generally 0) is not a valid map entry! -template -inline T get_if_there(const std::map& inmap, const K& key, T default_value) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map::const_iterator map_iter; - map_iter iter = inmap.find(key); - if(iter == inmap.end()) - { - return default_value; - } - else - { - return iter->second; - } -}; - -// Useful for replacing the removeObj() functionality of LLDynamicArray -// Example: -// for (std::vector::iterator iter = mList.begin(); iter != mList.end(); ) -// { -// if ((*iter)->isMarkedForRemoval()) -// iter = vector_replace_with_last(mList, iter); -// else -// ++iter; -// } -template -inline typename std::vector::iterator vector_replace_with_last(std::vector& invec, typename std::vector::iterator iter) -{ - typename std::vector::iterator last = invec.end(); --last; - if (iter == invec.end()) - { - return iter; - } - else if (iter == last) - { - invec.pop_back(); - return invec.end(); - } - else - { - *iter = *last; - invec.pop_back(); - return iter; - } -}; - -// Example: -// vector_replace_with_last(mList, x); -template -inline bool vector_replace_with_last(std::vector& invec, const T& val) -{ - typename std::vector::iterator iter = std::find(invec.begin(), invec.end(), val); - if (iter != invec.end()) - { - typename std::vector::iterator last = invec.end(); --last; - *iter = *last; - invec.pop_back(); - return true; - } - return false; -} - -// Append N elements to the vector and return a pointer to the first new element. -template -inline T* vector_append(std::vector& invec, S32 N) -{ - U32 sz = invec.size(); - invec.resize(sz+N); - return &(invec[sz]); -} - -// call function f to n members starting at first. similar to std::for_each -template -Function ll_for_n(InputIter first, Size n, Function f) -{ - for ( ; n > 0; --n, ++first) - f(*first); - return f; -} - -// copy first to result n times, incrementing each as we go -template -OutputIter ll_copy_n(InputIter first, Size n, OutputIter result) -{ - for ( ; n > 0; --n, ++result, ++first) - *result = *first; - return result; -} - -// set *result = op(*f) for n elements of f -template -OutputIter ll_transform_n( - InputIter first, - Size n, - OutputIter result, - UnaryOp op) -{ - for ( ; n > 0; --n, ++result, ++first) - *result = op(*first); - return result; -} - - - -/* - * - * Copyright (c) 1994 - * Hewlett-Packard Company - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Hewlett-Packard Company makes no - * representations about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. - * - * - * Copyright (c) 1996-1998 - * Silicon Graphics Computer Systems, Inc. - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Silicon Graphics makes no - * representations about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. - */ - - -// helper to deal with the fact that MSDev does not package -// select... with the stl. Look up usage on the sgi website. - -template -struct _LLSelect1st -{ - const auto& operator()(const _Pair& __x) const { - return __x.first; - } -}; - -template -struct _LLSelect2nd -{ - const auto& operator()(const _Pair& __x) const { - return __x.second; - } -}; - -template struct llselect1st : public _LLSelect1st<_Pair> {}; -template struct llselect2nd : public _LLSelect2nd<_Pair> {}; - -// helper to deal with the fact that MSDev does not package -// compose... with the stl. Look up usage on the sgi website. - -template -class ll_unary_compose -{ -protected: - _Operation1 __op1; - _Operation2 __op2; -public: - ll_unary_compose(const _Operation1& __x, const _Operation2& __y) - : __op1(__x), __op2(__y) {} - template - auto - operator()(const _Op2Arg& __x) const { - return __op1(__op2(__x)); - } -}; - -template -inline ll_unary_compose<_Operation1,_Operation2> -llcompose1(const _Operation1& __op1, const _Operation2& __op2) -{ - return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); -} - -template -class ll_binary_compose -{ -protected: - _Operation1 _M_op1; - _Operation2 _M_op2; - _Operation3 _M_op3; -public: - ll_binary_compose(const _Operation1& __x, const _Operation2& __y, - const _Operation3& __z) - : _M_op1(__x), _M_op2(__y), _M_op3(__z) { } - template - auto - operator()(const OP2ARG& __x) const { - return _M_op1(_M_op2(__x), _M_op3(__x)); - } -}; - -template -inline ll_binary_compose<_Operation1, _Operation2, _Operation3> -llcompose2(const _Operation1& __op1, const _Operation2& __op2, - const _Operation3& __op3) -{ - return ll_binary_compose<_Operation1,_Operation2,_Operation3> - (__op1, __op2, __op3); -} - -// helpers to deal with the fact that MSDev does not package -// bind... with the stl. Again, this is from sgi. -template -class llbinder1st -{ -protected: - _Operation op; - _Arg1 value; -public: - llbinder1st(const _Operation& __x, const _Arg1& __y) - : op(__x), value(__y) {} - template - auto - operator()(const _Arg2& __x) const { - return op(value, __x); - } -}; - -template -inline auto -llbind1st(const _Operation& __oper, const _Tp& __x) -{ - return llbinder1st<_Operation, _Tp>(__oper, __x); -} - -template -class llbinder2nd -{ -protected: - _Operation op; - _Arg2 value; -public: - llbinder2nd(const _Operation& __x, - const _Arg2& __y) - : op(__x), value(__y) {} - template - auto - operator()(const _Arg1& __x) const { - return op(__x, value); - } -}; - -template -inline auto -llbind2nd(const _Operation& __oper, const _Tp& __x) -{ - return llbinder2nd<_Operation, _Tp>(__oper, __x); -} - -/** - * Compare std::type_info* pointers a la std::less. We break this out as a - * separate function for use in two different std::less specializations. - */ -inline -bool before(const std::type_info* lhs, const std::type_info* rhs) -{ -#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4)) - // If we're building on Linux with gcc, and it's either gcc 3.x or - // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on - // Mac too, and some people build with gcc on Windows (cygwin or mingw). - // On Linux, different load modules may produce different type_info* - // pointers for the same type. Have to compare name strings to get good - // results. - return strcmp(lhs->name(), rhs->name()) < 0; -#else // not Linux, or gcc 4.4+ - // Just use before(), as we normally would - return lhs->before(*rhs); -#endif -} - -/** - * Specialize std::less to use std::type_info::before(). - * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info* - * because, on Linux, you might get different std::type_info* pointers for the - * same type (from different load modules)! - */ -namespace std -{ - template <> - struct less - { - bool operator()(const std::type_info* lhs, const std::type_info* rhs) const - { - return before(lhs, rhs); - } - }; - - template <> - struct less - { - bool operator()(std::type_info* lhs, std::type_info* rhs) const - { - return before(lhs, rhs); - } - }; -} // std - - -/** - * Implementation for ll_template_cast() (q.v.). - * - * Default implementation: trying to cast two completely unrelated types - * returns 0. Typically you'd specify T and U as pointer types, but in fact T - * can be any type that can be initialized with 0. - */ -template -struct ll_template_cast_impl -{ - T operator()(U) - { - return 0; - } -}; - -/** - * ll_template_cast(some_value) is for use in a template function when - * some_value might be of arbitrary type, but you want to recognize type T - * specially. - * - * It's designed for use with pointer types. Example: - * @code - * struct SpecialClass - * { - * void someMethod(const std::string&) const; - * }; - * - * template - * void somefunc(const REALCLASS& instance) - * { - * const SpecialClass* ptr = ll_template_cast(&instance); - * if (ptr) - * { - * ptr->someMethod("Call method only available on SpecialClass"); - * } - * } - * @endcode - * - * Why is this better than dynamic_cast<>? Because unless OtherClass is - * polymorphic, the following won't even compile (gcc 4.0.1): - * @code - * OtherClass other; - * SpecialClass* ptr = dynamic_cast(&other); - * @endcode - * to say nothing of this: - * @code - * void function(int); - * SpecialClass* ptr = dynamic_cast(&function); - * @endcode - * ll_template_cast handles these kinds of cases by returning 0. - */ -template -T ll_template_cast(U value) -{ - return ll_template_cast_impl()(value); -} - -/** - * Implementation for ll_template_cast() (q.v.). - * - * Implementation for identical types: return same value. - */ -template -struct ll_template_cast_impl -{ - T operator()(T value) - { - return value; - } -}; - -/** - * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of - * type @c source, ll_template_cast(s) will return @c s -- - * presuming that @c source can be converted to @c dest by the normal rules of - * C++. - * - * By default, ll_template_cast(s) will return 0 unless @c s's - * type is literally identical to @c dest. (This is because of the - * straightforward application of template specialization rules.) That can - * lead to surprising results, e.g.: - * - * @code - * Foo myFoo; - * const Foo* fooptr = ll_template_cast(&myFoo); - * @endcode - * - * Here @c fooptr will be 0 because &myFoo is of type Foo* - * -- @em not const Foo*. (Declaring const Foo myFoo; would - * force the compiler to do the right thing.) - * - * More disappointingly: - * @code - * struct Base {}; - * struct Subclass: public Base {}; - * Subclass object; - * Base* ptr = ll_template_cast(&object); - * @endcode - * - * Here @c ptr will be 0 because &object is of type - * Subclass* rather than Base*. We @em want this cast to - * succeed, but without our help ll_template_cast can't recognize it. - * - * The following would suffice: - * @code - * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); - * ... - * Base* ptr = ll_template_cast(&object); - * @endcode - * - * However, as noted earlier, this is easily fooled: - * @code - * const Base* ptr = ll_template_cast(&object); - * @endcode - * would still produce 0 because we haven't yet seen: - * @code - * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); - * @endcode - * - * @TODO - * This macro should use Boost type_traits facilities for stripping and - * re-adding @c const and @c volatile qualifiers so that invoking - * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all - * permitted permutations. It's really not fair to the coder to require - * separate: - * @code - * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); - * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); - * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*); - * @endcode - * - * (Naturally we omit LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) - * because that's not permitted by normal C++ assignment anyway.) - */ -#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ -template <> \ -struct ll_template_cast_impl \ -{ \ - DEST operator()(SOURCE wrapper) \ - { \ - return wrapper; \ - } \ -} - - -#endif // LL_LLSTL_H +/** + * @file llstl.h + * @brief helper object & functions for use with the stl. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTL_H +#define LL_LLSTL_H + +#include "stdtypes.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef LL_LINUX +// For strcmp +#include +#endif +// Use to compare the first element only of a pair +// e.g. typedef std::set, compare_pair > some_pair_set_t; +template +struct compare_pair_first +{ + bool operator()(const std::pair& a, const std::pair& b) const + { + return a.first < b.first; + } +}; + +template +struct compare_pair_greater +{ + bool operator()(const std::pair& a, const std::pair& b) const + { + if (!(a.first < b.first)) + return true; + else if (!(b.first < a.first)) + return false; + else + return !(a.second < b.second); + } +}; + +// Use to compare the contents of two pointers (e.g. std::string*) +template +struct compare_pointer_contents +{ + typedef const T* Tptr; + bool operator()(const Tptr& a, const Tptr& b) const + { + return *a < *b; + } +}; + +// DeletePointer is a simple helper for deleting all pointers in a container. +// The general form is: +// +// std::for_each(cont.begin(), cont.end(), DeletePointer()); +// somemap.clear(); +// +// Don't forget to clear()! + +struct DeletePointer +{ + template void operator()(T* ptr) const + { + delete ptr; + } +}; +struct DeletePointerArray +{ + template void operator()(T* ptr) const + { + delete[] ptr; + } +}; + +// DeletePairedPointer is a simple helper for deleting all pointers in a map. +// The general form is: +// +// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer()); +// somemap.clear(); // Don't leave dangling pointers around + +struct DeletePairedPointer +{ + template void operator()(T &ptr) const + { + delete ptr.second; + ptr.second = NULL; + } +}; +struct DeletePairedPointerArray +{ + template void operator()(T &ptr) const + { + delete[] ptr.second; + ptr.second = NULL; + } +}; + + +// Alternate version of the above so that has a more cumbersome +// syntax, but it can be used with compositional functors. +// NOTE: The functor retuns a bool because msdev bombs during the +// composition if you return void. Once we upgrade to a newer +// compiler, the second unary_function template parameter can be set +// to void. +// +// Here's a snippet showing how you use this object: +// +// typedef std::map map_type; +// map_type widget_map; +// ... // add elements +// // delete them all +// for_each(widget_map.begin(), +// widget_map.end(), +// llcompose1(DeletePointerFunctor(), +// llselect2nd())); + +template +struct DeletePointerFunctor +{ + bool operator()(T* ptr) const + { + delete ptr; + return true; + } +}; + +// See notes about DeleteArray for why you should consider avoiding this. +template +struct DeleteArrayFunctor +{ + bool operator()(T* ptr) const + { + delete[] ptr; + return true; + } +}; + +// CopyNewPointer is a simple helper which accepts a pointer, and +// returns a new pointer built with the copy constructor. Example: +// +// transform(in.begin(), in.end(), out.end(), CopyNewPointer()); + +struct CopyNewPointer +{ + template T* operator()(const T* ptr) const + { + return new T(*ptr); + } +}; + +template +void delete_and_clear(std::list& list) +{ + std::for_each(list.begin(), list.end(), DeletePointer()); + list.clear(); +} + +template +void delete_and_clear(std::vector& vector) +{ + std::for_each(vector.begin(), vector.end(), DeletePointer()); + vector.clear(); +} + +template +void delete_and_clear(std::set& set) +{ + std::for_each(set.begin(), set.end(), DeletePointer()); + set.clear(); +} + +template +void delete_and_clear(std::map& map) +{ + std::for_each(map.begin(), map.end(), DeletePairedPointer()); + map.clear(); +} + +template +void delete_and_clear(T*& ptr) +{ + delete ptr; + ptr = NULL; +} + + +template +void delete_and_clear_array(T*& ptr) +{ + delete[] ptr; + ptr = NULL; +} + +// Simple function to help with finding pointers in maps. +// For example: +// typedef map_t; +// std::map foo; +// foo[18] = "there"; +// foo[2] = "hello"; +// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" +// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL +template +inline T* get_ptr_in_map(const std::map& inmap, const K& key) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return NULL; + } + else + { + return iter->second; + } +}; + +// helper function which returns true if key is in inmap. +template +inline bool is_in_map(const std::map& inmap, const K& key) +{ + if(inmap.find(key) == inmap.end()) + { + return false; + } + else + { + return true; + } +} + +// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor. +// To replace LLSkipMap getIfThere, use: +// get_if_there(map, key, 0) +// WARNING: Make sure default_value (generally 0) is not a valid map entry! +template +inline T get_if_there(const std::map& inmap, const K& key, T default_value) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return default_value; + } + else + { + return iter->second; + } +}; + +// Useful for replacing the removeObj() functionality of LLDynamicArray +// Example: +// for (std::vector::iterator iter = mList.begin(); iter != mList.end(); ) +// { +// if ((*iter)->isMarkedForRemoval()) +// iter = vector_replace_with_last(mList, iter); +// else +// ++iter; +// } +template +inline typename std::vector::iterator vector_replace_with_last(std::vector& invec, typename std::vector::iterator iter) +{ + typename std::vector::iterator last = invec.end(); --last; + if (iter == invec.end()) + { + return iter; + } + else if (iter == last) + { + invec.pop_back(); + return invec.end(); + } + else + { + *iter = *last; + invec.pop_back(); + return iter; + } +}; + +// Example: +// vector_replace_with_last(mList, x); +template +inline bool vector_replace_with_last(std::vector& invec, const T& val) +{ + typename std::vector::iterator iter = std::find(invec.begin(), invec.end(), val); + if (iter != invec.end()) + { + typename std::vector::iterator last = invec.end(); --last; + *iter = *last; + invec.pop_back(); + return true; + } + return false; +} + +// Append N elements to the vector and return a pointer to the first new element. +template +inline T* vector_append(std::vector& invec, S32 N) +{ + U32 sz = invec.size(); + invec.resize(sz+N); + return &(invec[sz]); +} + +// call function f to n members starting at first. similar to std::for_each +template +Function ll_for_n(InputIter first, Size n, Function f) +{ + for ( ; n > 0; --n, ++first) + f(*first); + return f; +} + +// copy first to result n times, incrementing each as we go +template +OutputIter ll_copy_n(InputIter first, Size n, OutputIter result) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = *first; + return result; +} + +// set *result = op(*f) for n elements of f +template +OutputIter ll_transform_n( + InputIter first, + Size n, + OutputIter result, + UnaryOp op) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = op(*first); + return result; +} + + + +/* + * + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + + +// helper to deal with the fact that MSDev does not package +// select... with the stl. Look up usage on the sgi website. + +template +struct _LLSelect1st +{ + const auto& operator()(const _Pair& __x) const { + return __x.first; + } +}; + +template +struct _LLSelect2nd +{ + const auto& operator()(const _Pair& __x) const { + return __x.second; + } +}; + +template struct llselect1st : public _LLSelect1st<_Pair> {}; +template struct llselect2nd : public _LLSelect2nd<_Pair> {}; + +// helper to deal with the fact that MSDev does not package +// compose... with the stl. Look up usage on the sgi website. + +template +class ll_unary_compose +{ +protected: + _Operation1 __op1; + _Operation2 __op2; +public: + ll_unary_compose(const _Operation1& __x, const _Operation2& __y) + : __op1(__x), __op2(__y) {} + template + auto + operator()(const _Op2Arg& __x) const { + return __op1(__op2(__x)); + } +}; + +template +inline ll_unary_compose<_Operation1,_Operation2> +llcompose1(const _Operation1& __op1, const _Operation2& __op2) +{ + return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); +} + +template +class ll_binary_compose +{ +protected: + _Operation1 _M_op1; + _Operation2 _M_op2; + _Operation3 _M_op3; +public: + ll_binary_compose(const _Operation1& __x, const _Operation2& __y, + const _Operation3& __z) + : _M_op1(__x), _M_op2(__y), _M_op3(__z) { } + template + auto + operator()(const OP2ARG& __x) const { + return _M_op1(_M_op2(__x), _M_op3(__x)); + } +}; + +template +inline ll_binary_compose<_Operation1, _Operation2, _Operation3> +llcompose2(const _Operation1& __op1, const _Operation2& __op2, + const _Operation3& __op3) +{ + return ll_binary_compose<_Operation1,_Operation2,_Operation3> + (__op1, __op2, __op3); +} + +// helpers to deal with the fact that MSDev does not package +// bind... with the stl. Again, this is from sgi. +template +class llbinder1st +{ +protected: + _Operation op; + _Arg1 value; +public: + llbinder1st(const _Operation& __x, const _Arg1& __y) + : op(__x), value(__y) {} + template + auto + operator()(const _Arg2& __x) const { + return op(value, __x); + } +}; + +template +inline auto +llbind1st(const _Operation& __oper, const _Tp& __x) +{ + return llbinder1st<_Operation, _Tp>(__oper, __x); +} + +template +class llbinder2nd +{ +protected: + _Operation op; + _Arg2 value; +public: + llbinder2nd(const _Operation& __x, + const _Arg2& __y) + : op(__x), value(__y) {} + template + auto + operator()(const _Arg1& __x) const { + return op(__x, value); + } +}; + +template +inline auto +llbind2nd(const _Operation& __oper, const _Tp& __x) +{ + return llbinder2nd<_Operation, _Tp>(__oper, __x); +} + +/** + * Compare std::type_info* pointers a la std::less. We break this out as a + * separate function for use in two different std::less specializations. + */ +inline +bool before(const std::type_info* lhs, const std::type_info* rhs) +{ +#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4)) + // If we're building on Linux with gcc, and it's either gcc 3.x or + // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on + // Mac too, and some people build with gcc on Windows (cygwin or mingw). + // On Linux, different load modules may produce different type_info* + // pointers for the same type. Have to compare name strings to get good + // results. + return strcmp(lhs->name(), rhs->name()) < 0; +#else // not Linux, or gcc 4.4+ + // Just use before(), as we normally would + return lhs->before(*rhs); +#endif +} + +/** + * Specialize std::less to use std::type_info::before(). + * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info* + * because, on Linux, you might get different std::type_info* pointers for the + * same type (from different load modules)! + */ +namespace std +{ + template <> + struct less + { + bool operator()(const std::type_info* lhs, const std::type_info* rhs) const + { + return before(lhs, rhs); + } + }; + + template <> + struct less + { + bool operator()(std::type_info* lhs, std::type_info* rhs) const + { + return before(lhs, rhs); + } + }; +} // std + + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Default implementation: trying to cast two completely unrelated types + * returns 0. Typically you'd specify T and U as pointer types, but in fact T + * can be any type that can be initialized with 0. + */ +template +struct ll_template_cast_impl +{ + T operator()(U) + { + return 0; + } +}; + +/** + * ll_template_cast(some_value) is for use in a template function when + * some_value might be of arbitrary type, but you want to recognize type T + * specially. + * + * It's designed for use with pointer types. Example: + * @code + * struct SpecialClass + * { + * void someMethod(const std::string&) const; + * }; + * + * template + * void somefunc(const REALCLASS& instance) + * { + * const SpecialClass* ptr = ll_template_cast(&instance); + * if (ptr) + * { + * ptr->someMethod("Call method only available on SpecialClass"); + * } + * } + * @endcode + * + * Why is this better than dynamic_cast<>? Because unless OtherClass is + * polymorphic, the following won't even compile (gcc 4.0.1): + * @code + * OtherClass other; + * SpecialClass* ptr = dynamic_cast(&other); + * @endcode + * to say nothing of this: + * @code + * void function(int); + * SpecialClass* ptr = dynamic_cast(&function); + * @endcode + * ll_template_cast handles these kinds of cases by returning 0. + */ +template +T ll_template_cast(U value) +{ + return ll_template_cast_impl()(value); +} + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Implementation for identical types: return same value. + */ +template +struct ll_template_cast_impl +{ + T operator()(T value) + { + return value; + } +}; + +/** + * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of + * type @c source, ll_template_cast(s) will return @c s -- + * presuming that @c source can be converted to @c dest by the normal rules of + * C++. + * + * By default, ll_template_cast(s) will return 0 unless @c s's + * type is literally identical to @c dest. (This is because of the + * straightforward application of template specialization rules.) That can + * lead to surprising results, e.g.: + * + * @code + * Foo myFoo; + * const Foo* fooptr = ll_template_cast(&myFoo); + * @endcode + * + * Here @c fooptr will be 0 because &myFoo is of type Foo* + * -- @em not const Foo*. (Declaring const Foo myFoo; would + * force the compiler to do the right thing.) + * + * More disappointingly: + * @code + * struct Base {}; + * struct Subclass: public Base {}; + * Subclass object; + * Base* ptr = ll_template_cast(&object); + * @endcode + * + * Here @c ptr will be 0 because &object is of type + * Subclass* rather than Base*. We @em want this cast to + * succeed, but without our help ll_template_cast can't recognize it. + * + * The following would suffice: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * ... + * Base* ptr = ll_template_cast(&object); + * @endcode + * + * However, as noted earlier, this is easily fooled: + * @code + * const Base* ptr = ll_template_cast(&object); + * @endcode + * would still produce 0 because we haven't yet seen: + * @code + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * @endcode + * + * @TODO + * This macro should use Boost type_traits facilities for stripping and + * re-adding @c const and @c volatile qualifiers so that invoking + * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all + * permitted permutations. It's really not fair to the coder to require + * separate: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*); + * @endcode + * + * (Naturally we omit LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) + * because that's not permitted by normal C++ assignment anyway.) + */ +#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ +template <> \ +struct ll_template_cast_impl \ +{ \ + DEST operator()(SOURCE wrapper) \ + { \ + return wrapper; \ + } \ +} + + +#endif // LL_LLSTL_H diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index b3fc9b484a..bbb6aa2c20 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1,1756 +1,1756 @@ -/** - * @file llstring.cpp - * @brief String utility functions and the std::string class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llstring.h" -#include "llerror.h" -#include "llfasttimer.h" -#include "llsd.h" -#include - -#if LL_WINDOWS -#include "llwin32headerslean.h" -#include // for WideCharToMultiByte -#endif - -std::string ll_safe_string(const char* in) -{ - if(in) return std::string(in); - return std::string(); -} - -std::string ll_safe_string(const char* in, S32 maxlen) -{ - if(in && maxlen > 0 ) return std::string(in, maxlen); - - return std::string(); -} - -bool is_char_hex(char hex) -{ - if((hex >= '0') && (hex <= '9')) - { - return true; - } - else if((hex >= 'a') && (hex <='f')) - { - return true; - } - else if((hex >= 'A') && (hex <='F')) - { - return true; - } - return false; // uh - oh, not hex any more... -} - -U8 hex_as_nybble(char hex) -{ - if((hex >= '0') && (hex <= '9')) - { - return (U8)(hex - '0'); - } - else if((hex >= 'a') && (hex <='f')) - { - return (U8)(10 + hex - 'a'); - } - else if((hex >= 'A') && (hex <='F')) - { - return (U8)(10 + hex - 'A'); - } - return 0; // uh - oh, not hex any more... -} - -bool iswindividual(llwchar elem) -{ - U32 cur_char = (U32)elem; - bool result = false; - if (0x2E80<= cur_char && cur_char <= 0x9FFF) - { - result = true; - } - else if (0xAC00<= cur_char && cur_char <= 0xD7A0 ) - { - result = true; - } - else if (0xF900<= cur_char && cur_char <= 0xFA60 ) - { - result = true; - } - return result; -} - -bool _read_file_into_string(std::string& str, const std::string& filename) -{ - llifstream ifs(filename.c_str(), llifstream::binary); - if (!ifs.is_open()) - { - LL_INFOS() << "Unable to open file " << filename << LL_ENDL; - return false; - } - - std::ostringstream oss; - - oss << ifs.rdbuf(); - str = oss.str(); - ifs.close(); - return true; -} - - - - -// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c -// for the Unicode implementation - this doesn't match because it was written before finding -// it. - - -std::ostream& operator<<(std::ostream &s, const LLWString &wstr) -{ - std::string utf8_str = wstring_to_utf8str(wstr); - s << utf8_str; - return s; -} - -std::string rawstr_to_utf8(const std::string& raw) -{ - LLWString wstr(utf8str_to_wstring(raw)); - return wstring_to_utf8str(wstr); -} - -std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) -{ - U32 cur_char = (U32)in_char; - char* base = outchars; - if (cur_char < 0x80) - { - *outchars++ = (U8)cur_char; - } - else if (cur_char < 0x800) - { - *outchars++ = 0xC0 | (cur_char >> 6); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x10000) - { - *outchars++ = 0xE0 | (cur_char >> 12); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x200000) - { - *outchars++ = 0xF0 | (cur_char >> 18); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x4000000) - { - *outchars++ = 0xF8 | (cur_char >> 24); - *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x80000000) - { - *outchars++ = 0xFC | (cur_char >> 30); - *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else - { - LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; - *outchars++ = LL_UNKNOWN_CHAR; - } - return outchars - base; -} - -auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) -{ - const U16* base = inchars; - U16 cur_char = *inchars++; - llwchar char32 = cur_char; - if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF)) - { - // Surrogates - char32 = ((llwchar)(cur_char - 0xD800)) << 10; - cur_char = *inchars++; - char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL; - } - else - { - char32 = (llwchar)cur_char; - } - *outchar = char32; - return inchars - base; -} - -llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) -{ - llutf16string out; - - S32 i = 0; - while (i < len) - { - U32 cur_char = utf32str[i]; - if (cur_char > 0xFFFF) - { - out += (0xD7C0 + (cur_char >> 10)); - out += (0xDC00 | (cur_char & 0x3FF)); - } - else - { - out += cur_char; - } - i++; - } - return out; -} - -llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) -{ - LLWString wstr = utf8str_to_wstring ( utf8str, len ); - return wstring_to_utf16str ( wstr ); -} - -LLWString utf16str_to_wstring(const U16* utf16str, size_t len) -{ - LLWString wout; - if (len == 0) return wout; - - S32 i = 0; - const U16* chars16 = utf16str; - while (i < len) - { - llwchar cur_char; - i += utf16chars_to_wchar(chars16+i, &cur_char); - wout += cur_char; - } - return wout; -} - -// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) -{ - S32 surrogate_pairs = 0; - // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): - const U16 *const utf16_chars = &(*(utf16str.begin())); - S32 i = 0; - while (i < utf16_len) - { - const U16 c = utf16_chars[i++]; - if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16 - { // Have first byte of a surrogate pair - if (i >= utf16_len) - { - break; - } - const U16 d = utf16_chars[i]; - if (d >= 0xDC00 && d <= 0xDFFF) - { // Have valid second byte of a surrogate pair - surrogate_pairs++; - i++; - } - } - } - return utf16_len - surrogate_pairs; -} - -// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen) -{ - const S32 end = llmin((S32)wstr.length(), woffset + wlen); - if (end < woffset) - { - return 0; - } - else - { - S32 length = end - woffset; - for (S32 i = woffset; i < end; i++) - { - if (wstr[i] >= 0x10000) - { - length++; - } - } - return length; - } -} - -// Given a wstring and an offset in it, returns the length as wstring (i.e., -// number of llwchars) of the longest substring that starts at the offset -// and whose equivalent utf-16 string does not exceeds the given utf16_length. -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned) -{ - const auto end = wstr.length(); - bool u{ false }; - S32 n = woffset + utf16_length; - S32 i = woffset; - while (i < end) - { - if (wstr[i] >= 0x10000) - { - --n; - } - if (i >= n) - { - u = (i > n); - break; - } - i++; - } - if (unaligned) - { - *unaligned = u; - } - return i - woffset; -} - -S32 wchar_utf8_length(const llwchar wc) -{ - if (wc < 0x80) - { - return 1; - } - else if (wc < 0x800) - { - return 2; - } - else if (wc < 0x10000) - { - return 3; - } - else if (wc < 0x200000) - { - return 4; - } - else if (wc < 0x4000000) - { - return 5; - } - else - { - return 6; - } -} - -std::string wchar_utf8_preview(const llwchar wc) -{ - std::ostringstream oss; - oss << std::hex << std::uppercase << (U32)wc; - - U8 out_bytes[8]; - U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); - - if (size > 1) - { - oss << " ["; - for (U32 i = 0; i < size; ++i) - { - if (i) - { - oss << ", "; - } - oss << (int)out_bytes[i]; - } - oss << "]"; - } - - return oss.str(); -} - -S32 wstring_utf8_length(const LLWString& wstr) -{ - S32 len = 0; - for (S32 i = 0; i < (S32)wstr.length(); i++) - { - len += wchar_utf8_length(wstr[i]); - } - return len; -} - -LLWString utf8str_to_wstring(const char* utf8str, size_t len) -{ - LLWString wout; - - S32 i = 0; - while (i < len) - { - llwchar unichar; - U8 cur_char = utf8str[i]; - - if (cur_char < 0x80) - { - // Ascii character, just add it - unichar = cur_char; - } - else - { - S32 cont_bytes = 0; - if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32 - { - unichar = (0x1F&cur_char); - cont_bytes = 1; - } - else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32 - { - unichar = (0x0F&cur_char); - cont_bytes = 2; - } - else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32 - { - unichar = (0x07&cur_char); - cont_bytes = 3; - } - else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32 - { - unichar = (0x03&cur_char); - cont_bytes = 4; - } - else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32 - { - unichar = (0x01&cur_char); - cont_bytes = 5; - } - else - { - wout += LL_UNKNOWN_CHAR; - ++i; - continue; - } - - // Check that this character doesn't go past the end of the string - auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes); - do - { - ++i; - - cur_char = utf8str[i]; - if ( (cur_char >> 6) == 0x2 ) - { - unichar <<= 6; - unichar += (0x3F&cur_char); - } - else - { - // Malformed sequence - roll back to look at this as a new char - unichar = LL_UNKNOWN_CHAR; - --i; - break; - } - } while(i < end); - - // Handle overlong characters and NULL characters - if ( ((cont_bytes == 1) && (unichar < 0x80)) - || ((cont_bytes == 2) && (unichar < 0x800)) - || ((cont_bytes == 3) && (unichar < 0x10000)) - || ((cont_bytes == 4) && (unichar < 0x200000)) - || ((cont_bytes == 5) && (unichar < 0x4000000)) ) - { - unichar = LL_UNKNOWN_CHAR; - } - } - - wout += unichar; - ++i; - } - return wout; -} - -std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) -{ - std::string out; - - S32 i = 0; - while (i < len) - { - char tchars[8]; /* Flawfinder: ignore */ - auto n = wchar_to_utf8chars(utf32str[i], tchars); - tchars[n] = 0; - out += tchars; - i++; - } - return out; -} - -std::string utf16str_to_utf8str(const U16* utf16str, size_t len) -{ - return wstring_to_utf8str(utf16str_to_wstring(utf16str, len)); -} - -std::string utf8str_trim(const std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::trim(wstr); - return wstring_to_utf8str(wstr); -} - - -std::string utf8str_tolower(const std::string& utf8str) -{ - LLWString out_str = utf8str_to_wstring(utf8str); - LLWStringUtil::toLower(out_str); - return wstring_to_utf8str(out_str); -} - - -S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs) -{ - LLWString wlhs = utf8str_to_wstring(lhs); - LLWString wrhs = utf8str_to_wstring(rhs); - return LLWStringUtil::compareInsensitive(wlhs, wrhs); -} - -std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) -{ - if (0 == max_len) - { - return std::string(); - } - if ((S32)utf8str.length() <= max_len) - { - return utf8str; - } - else - { - S32 cur_char = max_len; - - // If we're ASCII, we don't need to do anything - if ((U8)utf8str[cur_char] > 0x7f) - { - // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back - // to the first character - while (0x80 == (0xc0 & utf8str[cur_char])) - { - cur_char--; - // Keep moving forward until we hit the first char; - if (cur_char == 0) - { - // Make sure we don't trash memory if we've got a bogus string. - break; - } - } - } - // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars - return utf8str.substr(0, cur_char); - } -} - -std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len) -{ - if (0 == symbol_len) - { - return std::string(); - } - if ((S32)utf8str.length() <= symbol_len) - { - return utf8str; - } - else - { - int len = 0, byteIndex = 0; - const char* aStr = utf8str.c_str(); - size_t origSize = utf8str.size(); - - for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++) - { - if ((aStr[byteIndex] & 0xc0) != 0x80) - { - len += 1; - } - } - return utf8str.substr(0, byteIndex); - } -} - -std::string utf8str_substChar( - const std::string& utf8str, - const llwchar target_char, - const llwchar replace_char) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::replaceChar(wstr, target_char, replace_char); - //wstr = wstring_substChar(wstr, target_char, replace_char); - return wstring_to_utf8str(wstr); -} - -std::string utf8str_makeASCII(const std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::_makeASCII(wstr); - return wstring_to_utf8str(wstr); -} - -std::string mbcsstring_makeASCII(const std::string& wstr) -{ - // Replace non-ASCII chars with replace_char - std::string out_str = wstr; - for (S32 i = 0; i < (S32)out_str.length(); i++) - { - if ((U8)out_str[i] > 0x7f) - { - out_str[i] = LL_UNKNOWN_CHAR; - } - } - return out_str; -} - -std::string utf8str_removeCRLF(const std::string& utf8str) -{ - if (0 == utf8str.length()) - { - return std::string(); - } - const char CR = 13; - - std::string out; - out.reserve(utf8str.length()); - const S32 len = (S32)utf8str.length(); - for( S32 i = 0; i < len; i++ ) - { - if( utf8str[i] != CR ) - { - out.push_back(utf8str[i]); - } - } - return out; -} - -llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) -{ - switch (length) - { - case 2: - return ((utf8str[offset] & 0x1F) << 6) + - (utf8str[offset + 1] & 0x3F); - case 3: - return ((utf8str[offset] & 0x0F) << 12) + - ((utf8str[offset + 1] & 0x3F) << 6) + - (utf8str[offset + 2] & 0x3F); - case 4: - return ((utf8str[offset] & 0x07) << 18) + - ((utf8str[offset + 1] & 0x3F) << 12) + - ((utf8str[offset + 2] & 0x3F) << 6) + - (utf8str[offset + 3] & 0x3F); - case 5: - return ((utf8str[offset] & 0x03) << 24) + - ((utf8str[offset + 1] & 0x3F) << 18) + - ((utf8str[offset + 2] & 0x3F) << 12) + - ((utf8str[offset + 3] & 0x3F) << 6) + - (utf8str[offset + 4] & 0x3F); - case 6: - return ((utf8str[offset] & 0x01) << 30) + - ((utf8str[offset + 1] & 0x3F) << 24) + - ((utf8str[offset + 2] & 0x3F) << 18) + - ((utf8str[offset + 3] & 0x3F) << 12) + - ((utf8str[offset + 4] & 0x3F) << 6) + - (utf8str[offset + 5] & 0x3F); - case 7: - return ((utf8str[offset + 1] & 0x03) << 30) + - ((utf8str[offset + 2] & 0x3F) << 24) + - ((utf8str[offset + 3] & 0x3F) << 18) + - ((utf8str[offset + 4] & 0x3F) << 12) + - ((utf8str[offset + 5] & 0x3F) << 6) + - (utf8str[offset + 6] & 0x3F); - } - return LL_UNKNOWN_CHAR; -} - -std::string utf8str_showBytesUTF8(const std::string& utf8str) -{ - std::string result; - - bool in_sequence = false; - size_t sequence_size = 0; - size_t byte_index = 0; - size_t source_length = utf8str.size(); - - auto open_sequence = [&]() - { - if (!result.empty() && result.back() != '\n') - result += '\n'; // Use LF as a separator before new UTF-8 sequence - result += '['; - in_sequence = true; - }; - - auto close_sequence = [&]() - { - llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); - if (unicode != LL_UNKNOWN_CHAR) - { - result += llformat("+%04X", unicode); - } - result += ']'; - in_sequence = false; - sequence_size = 0; - }; - - while (byte_index < source_length) - { - U8 byte = utf8str[byte_index]; - if (byte >= 0x80) // Part of an UTF-8 sequence - { - if (!in_sequence) // Start new UTF-8 sequence - { - open_sequence(); - } - else if (byte >= 0xC0) // Start another UTF-8 sequence - { - close_sequence(); - open_sequence(); - } - else // Continue the same UTF-8 sequence - { - result += '.'; - } - result += llformat("%02X", byte); // The byte is represented in hexadecimal form - ++sequence_size; - } - else // ASCII symbol is represented as a character - { - if (in_sequence) // End of UTF-8 sequence - { - close_sequence(); - if (byte != '\n') - { - result += '\n'; // Use LF as a separator between UTF-8 and ASCII - } - } - result += byte; - } - ++byte_index; - } - - if (in_sequence) // End of UTF-8 sequence - { - close_sequence(); - } - - return result; -} - -// Search for any emoji symbol, return true if found -bool wstring_has_emoji(const LLWString& wstr) -{ - for (const llwchar& wch : wstr) - { - if (LLStringOps::isEmoji(wch)) - return true; - } - - return false; -} - -// Cut emoji symbols if exist -bool wstring_remove_emojis(LLWString& wstr) -{ - bool found = false; - for (size_t i = 0; i < wstr.size(); ++i) - { - if (LLStringOps::isEmoji(wstr[i])) - { - wstr.erase(i--, 1); - found = true; - } - } - return found; -} - -// Cut emoji symbols if exist -bool utf8str_remove_emojis(std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - if (!wstring_remove_emojis(wstr)) - return false; - utf8str = wstring_to_utf8str(wstr); - return true; -} - -#if LL_WINDOWS -unsigned int ll_wstring_default_code_page() -{ - return CP_UTF8; -} - -std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page) -{ - std::string out; - if(in) - { - int len_out = WideCharToMultiByte( - code_page, - 0, - in, - len_in, - NULL, - 0, - 0, - 0); - // We will need two more bytes for the double NULL ending - // created in WideCharToMultiByte(). - char* pout = new char [len_out + 2]; - memset(pout, 0, len_out + 2); - if(pout) - { - WideCharToMultiByte( - code_page, - 0, - in, - len_in, - pout, - len_out, - 0, - 0); - out.assign(pout); - delete[] pout; - } - } - return out; -} - -std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page) -{ - // From review: - // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, - // plus one for a null terminator, and be guaranteed to not overflow. - - // Normally, I'd call that sort of thing premature optimization, - // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. -// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); - - // reserve an output buffer that will be destroyed on exit, with a place - // to put NULL terminator - std::vector w_out(len + 1); - - memset(&w_out[0], 0, w_out.size()); - int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, - &w_out[0], w_out.size() - 1); - - //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. - w_out[real_output_str_len] = 0; - - // construct string from our temporary output buffer - return {&w_out[0]}; -} - -LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len) -{ - // Whether or not std::wstring and llutf16string are distinct types, they - // both hold UTF-16LE characters. (See header file comments.) Pretend this - // wchar_t* sequence is really a U16* sequence and use the conversion we - // define above. - return utf16str_to_wstring(reinterpret_cast(in), len); -} - -std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len) -{ - // first, convert to llutf16string, for which we have a real implementation - auto utf16str{ wstring_to_utf16str(in, len) }; - // then, because each U16 char must be UTF-16LE encoded, pretend the U16* - // string pointer is a wchar_t* and instantiate a std::wstring of the same - // length. - return { reinterpret_cast(utf16str.c_str()), utf16str.length() }; -} - -std::string ll_convert_string_to_utf8_string(const std::string& in) -{ - // If you pass code_page, you must also pass length, otherwise the code - // page parameter will be mistaken for length. - auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP); - // CP_UTF8 is default -- see ll_wstring_default_code_page() above. - return ll_convert_wide_to_string(w_mesg); -} - -namespace -{ - -void HeapFree_deleter(void* ptr) -{ - // instead of LocalFree(), per https://stackoverflow.com/a/31541205 - HeapFree(GetProcessHeap(), NULL, ptr); -} - -} // anonymous namespace - -template<> -std::wstring windows_message(DWORD error) -{ - // derived from https://stackoverflow.com/a/455533 - wchar_t* rawptr = nullptr; - auto okay = FormatMessageW( - // use system message tables for GetLastError() codes - FORMAT_MESSAGE_FROM_SYSTEM | - // internally allocate buffer and return its pointer - FORMAT_MESSAGE_ALLOCATE_BUFFER | - // you cannot pass insertion parameters (thanks Gandalf) - FORMAT_MESSAGE_IGNORE_INSERTS | - // ignore line breaks in message definition text - FORMAT_MESSAGE_MAX_WIDTH_MASK, - NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM - error, // dwMessageId - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId - (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t* - 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER - NULL); // Arguments, unused - - // make a unique_ptr from rawptr so it gets cleaned up properly - std::unique_ptr bufferptr(rawptr, HeapFree_deleter); - - if (okay && bufferptr) - { - // got the message, return it ('okay' is length in characters) - return { bufferptr.get(), okay }; - } - - // did not get the message, synthesize one - auto format_message_error = GetLastError(); - std::wostringstream out; - out << L"GetLastError() " << error << L" (FormatMessageW() failed with " - << format_message_error << L")"; - return out.str(); -} - -std::optional llstring_getoptenv(const std::string& key) -{ - auto wkey = ll_convert_string_to_wide(key); - // Take a wild guess as to how big the buffer should be. - std::vector buffer(1024); - auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); - // If our initial guess was too short, n will indicate the size (in - // wchar_t's) that buffer should have been, including the terminating nul. - if (n > (buffer.size() - 1)) - { - // make it big enough - buffer.resize(n); - // and try again - n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); - } - // did that (ultimately) succeed? - if (n) - { - // great, return populated std::optional - return std::make_optional(&buffer[0]); - } - - // not successful - auto last_error = GetLastError(); - // Don't bother warning for NOT_FOUND; that's an expected case - if (last_error != ERROR_ENVVAR_NOT_FOUND) - { - LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " - << windows_message(last_error) << LL_ENDL; - } - // return empty std::optional - return {}; -} - -#else // ! LL_WINDOWS - -std::optional llstring_getoptenv(const std::string& key) -{ - auto found = getenv(key.c_str()); - if (found) - { - // return populated std::optional - return std::make_optional(found); - } - else - { - // return empty std::optional - return {}; - } -} - -#endif // ! LL_WINDOWS - -long LLStringOps::sPacificTimeOffset = 0; -long LLStringOps::sLocalTimeOffset = 0; -bool LLStringOps::sPacificDaylightTime = 0; -std::map LLStringOps::datetimeToCodes; - -std::vector LLStringOps::sWeekDayList; -std::vector LLStringOps::sWeekDayShortList; -std::vector LLStringOps::sMonthList; -std::vector LLStringOps::sMonthShortList; - - -std::string LLStringOps::sDayFormat; -std::string LLStringOps::sAM; -std::string LLStringOps::sPM; - -// static -bool LLStringOps::isEmoji(llwchar a) -{ -#if 0 // Do not consider special characters that might have a corresponding - // glyph in the monochorme fallback fonts as a "genuine" emoji. HB - return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) || - (a >= 0x1f000 && a < 0x20000); -#else - // These are indeed "genuine" emojis, we *do want* rendered as such. HB - return a >= 0x1f000 && a < 0x20000; -#endif - } - -S32 LLStringOps::collate(const llwchar* a, const llwchar* b) -{ - #if LL_WINDOWS - // in Windows, wide string functions operator on 16-bit strings, - // not the proper 32 bit wide string - return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); - #else - return wcscoll(a, b); - #endif -} - -void LLStringOps::setupDatetimeInfo (bool daylight) -{ - time_t nowT, localT, gmtT; - struct tm * tmpT; - - nowT = time (NULL); - - tmpT = gmtime (&nowT); - gmtT = mktime (tmpT); - - tmpT = localtime (&nowT); - localT = mktime (tmpT); - - sLocalTimeOffset = (long) (gmtT - localT); - if (tmpT->tm_isdst) - { - sLocalTimeOffset -= 60 * 60; // 1 hour - } - - sPacificDaylightTime = daylight; - sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; - - datetimeToCodes["wkday"] = "%a"; // Thu - datetimeToCodes["weekday"] = "%A"; // Thursday - datetimeToCodes["year4"] = "%Y"; // 2009 - datetimeToCodes["year"] = "%Y"; // 2009 - datetimeToCodes["year2"] = "%y"; // 09 - datetimeToCodes["mth"] = "%b"; // Aug - datetimeToCodes["month"] = "%B"; // August - datetimeToCodes["mthnum"] = "%m"; // 08 - datetimeToCodes["day"] = "%d"; // 31 - datetimeToCodes["sday"] = "%-d"; // 9 - datetimeToCodes["hour24"] = "%H"; // 14 - datetimeToCodes["hour"] = "%H"; // 14 - datetimeToCodes["hour12"] = "%I"; // 02 - datetimeToCodes["min"] = "%M"; // 59 - datetimeToCodes["ampm"] = "%p"; // AM - datetimeToCodes["second"] = "%S"; // 59 - datetimeToCodes["timezone"] = "%Z"; // PST -} - -void tokenizeStringToArray(const std::string& data, std::vector& output) -{ - output.clear(); - size_t length = data.size(); - - // tokenize it and put it in the array - std::string cur_word; - for(size_t i = 0; i < length; ++i) - { - if(data[i] == ':') - { - output.push_back(cur_word); - cur_word.clear(); - } - else - { - cur_word.append(1, data[i]); - } - } - output.push_back(cur_word); -} - -void LLStringOps::setupWeekDaysNames(const std::string& data) -{ - tokenizeStringToArray(data,sWeekDayList); -} -void LLStringOps::setupWeekDaysShortNames(const std::string& data) -{ - tokenizeStringToArray(data,sWeekDayShortList); -} -void LLStringOps::setupMonthNames(const std::string& data) -{ - tokenizeStringToArray(data,sMonthList); -} -void LLStringOps::setupMonthShortNames(const std::string& data) -{ - tokenizeStringToArray(data,sMonthShortList); -} -void LLStringOps::setupDayFormat(const std::string& data) -{ - sDayFormat = data; -} - - -std::string LLStringOps::getDatetimeCode (std::string key) -{ - std::map::iterator iter; - - iter = datetimeToCodes.find (key); - if (iter != datetimeToCodes.end()) - { - return iter->second; - } - else - { - return std::string(""); - } -} - -std::string LLStringOps::getReadableNumber(F64 num) -{ - if (fabs(num)>=1e9) - { - return llformat("%.2lfB", num / 1e9); - } - else if (fabs(num)>=1e6) - { - return llformat("%.2lfM", num / 1e6); - } - else if (fabs(num)>=1e3) - { - return llformat("%.2lfK", num / 1e3); - } - else - { - return llformat("%.2lf", num); - } -} - -namespace LLStringFn -{ - // NOTE - this restricts output to ascii - void replace_nonprintable_in_ascii(std::basic_string& string, char replacement) - { - const char MIN = 0x20; - std::basic_string::size_type len = string.size(); - for(std::basic_string::size_type ii = 0; ii < len; ++ii) - { - if(string[ii] < MIN) - { - string[ii] = replacement; - } - } - } - - - // NOTE - this restricts output to ascii - void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, - char replacement) - { - const char MIN = 0x20; - const char PIPE = 0x7c; - std::basic_string::size_type len = str.size(); - for(std::basic_string::size_type ii = 0; ii < len; ++ii) - { - if( (str[ii] < MIN) || (str[ii] == PIPE) ) - { - str[ii] = replacement; - } - } - } - - // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on - // allowable code points for XML. Specifically, they are: - // 0x09, 0x0a, 0x0d, and 0x20 on up. JC - std::string strip_invalid_xml(const std::string& instr) - { - std::string output; - output.reserve( instr.size() ); - std::string::const_iterator it = instr.begin(); - while (it != instr.end()) - { - // Must compare as unsigned for >= - // Test most likely match first - const unsigned char c = (unsigned char)*it; - if ( c >= (unsigned char)0x20 // SPACE - || c == (unsigned char)0x09 // TAB - || c == (unsigned char)0x0a // LINE_FEED - || c == (unsigned char)0x0d ) // CARRIAGE_RETURN - { - output.push_back(c); - } - ++it; - } - return output; - } - - /** - * @brief Replace all control characters (c < 0x20) with replacement in - * string. - */ - void replace_ascii_controlchars(std::basic_string& string, char replacement) - { - const unsigned char MIN = 0x20; - std::basic_string::size_type len = string.size(); - for(std::basic_string::size_type ii = 0; ii < len; ++ii) - { - const unsigned char c = (unsigned char) string[ii]; - if(c < MIN) - { - string[ii] = replacement; - } - } - } -} - -//////////////////////////////////////////////////////////// - -// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime. -template<> -S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); - -//static -template<> -void LLStringUtil::getTokens(const std::string& instr, std::vector& tokens, const std::string& delims) -{ - // Starting at offset 0, scan forward for the next non-delimiter. We're - // done when the only characters left in 'instr' are delimiters. - for (std::string::size_type begIdx, endIdx = 0; - (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; ) - { - // Found a non-delimiter. After that, find the next delimiter. - endIdx = instr.find_first_of (delims, begIdx); - if (endIdx == std::string::npos) - { - // No more delimiters: this token extends to the end of the string. - endIdx = instr.length(); - } - - // extract the token between begIdx and endIdx; substr() needs length - std::string currToken(instr.substr(begIdx, endIdx - begIdx)); - LLStringUtil::trim (currToken); - tokens.push_back(currToken); - // next scan past delimiters starts at endIdx - } -} - -template<> -LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector& tokens) -{ - const std::string delims (","); - - // Find the first [ - size_type pos1 = instr.find('[', start); - if (pos1 == std::string::npos) - return std::string::npos; - - //Find the first ] after the initial [ - size_type pos2 = instr.find(']', pos1); - if (pos2 == std::string::npos) - return std::string::npos; - - // Find the last [ before ] in case of nested [[]] - pos1 = instr.find_last_of('[', pos2-1); - if (pos1 == std::string::npos || pos1 < start) - return std::string::npos; - - getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims); - start = pos2+1; - - return pos1; -} - -// static -template<> -bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions) -{ - // see if we have a replacement for the bracketed string (without the brackets) - // test first using has() because if we just look up with operator[] we get back an - // empty string even if the value is missing. We want to distinguish between - // missing replacements and deliberately empty replacement strings. - format_map_t::const_iterator iter = substitutions.find(token); - if (iter != substitutions.end()) - { - replacement = iter->second; - return true; - } - // if not, see if there's one WITH brackets - iter = substitutions.find(std::string("[" + token + "]")); - if (iter != substitutions.end()) - { - replacement = iter->second; - return true; - } - - return false; -} - -// static -template<> -bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions) -{ - // see if we have a replacement for the bracketed string (without the brackets) - // test first using has() because if we just look up with operator[] we get back an - // empty string even if the value is missing. We want to distinguish between - // missing replacements and deliberately empty replacement strings. - if (substitutions.has(token)) - { - replacement = substitutions[token].asString(); - return true; - } - // if not, see if there's one WITH brackets - else if (substitutions.has(std::string("[" + token + "]"))) - { - replacement = substitutions[std::string("[" + token + "]")].asString(); - return true; - } - - return false; -} - -//static -template<> -void LLStringUtil::setLocale(std::string inLocale) -{ - sLocale = inLocale; -}; - -//static -template<> -std::string LLStringUtil::getLocale(void) -{ - return sLocale; -}; - -// static -template<> -void LLStringUtil::formatNumber(std::string& numStr, std::string decimals) -{ - std::stringstream strStream; - S32 intDecimals = 0; - - convertToS32 (decimals, intDecimals); - if (!sLocale.empty()) - { - // std::locale() throws if the locale is unknown! (EXT-7926) - try - { - strStream.imbue(std::locale(sLocale.c_str())); - } catch (const std::exception &) - { - LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL; - } - } - - if (!intDecimals) - { - S32 intStr; - - if (convertToS32(numStr, intStr)) - { - strStream << intStr; - numStr = strStream.str(); - } - } - else - { - F32 floatStr; - - if (convertToF32(numStr, floatStr)) - { - strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr; - numStr = strStream.str(); - } - } -} - -// static -template<> -bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, - std::string param, S32 secFromEpoch) -{ - if (param == "local") // local - { - secFromEpoch -= LLStringOps::getLocalTimeOffset(); - } - else if (param != "utc") // slt - { - secFromEpoch -= LLStringOps::getPacificTimeOffset(); - } - - // if never fell into those two ifs above, param must be utc - if (secFromEpoch < 0) secFromEpoch = 0; - - LLDate datetime((F64)secFromEpoch); - std::string code = LLStringOps::getDatetimeCode (token); - - // special case to handle timezone - if (code == "%Z") { - if (param == "utc") - { - replacement = "GMT"; - } - else if (param == "local") - { - replacement = ""; // user knows their own timezone - } - else - { -#if 0 - // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09 - // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT - // "slt" = Second Life Time, which is deprecated. - // If not utc or user local time, fallback to Pacific time - replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; -#else - // SL-20370 : Steeltoe Linden : 29/Sep/23 - // Change "PDT" to "SLT" on menu bar - replacement = "SLT"; -#endif - } - return true; - } - - //EXT-7013 - //few codes are not suppotred by strtime function (example - weekdays for Japanise) - //so use predefined ones - - //if sWeekDayList is not empty than current locale doesn't support - //weekday name. - time_t loc_seconds = (time_t) secFromEpoch; - if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; - } - else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; - } - else if(LLStringOps::sMonthList.size() == 12 && code == "%B") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sMonthList[gmt->tm_mon]; - } - else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) - { - struct tm * gmt = gmtime (&loc_seconds); - LLStringUtil::format_map_t args; - args["[MDAY]"] = llformat ("%d", gmt->tm_mday); - replacement = LLStringOps::sDayFormat; - LLStringUtil::format(replacement, args); - } - else if (code == "%-d") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero - } - else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) - { - struct tm * gmt = gmtime (&loc_seconds); - if(gmt->tm_hour<12) - { - replacement = LLStringOps::sAM; - } - else - { - replacement = LLStringOps::sPM; - } - } - else - { - replacement = datetime.toHTTPDateString(code); - } - - // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format - // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). - // We could have used '%l' format instead, but it's not supported by Windows. - if(code == "%I" && token == "hour12" && replacement.at(0) == '0') - { - replacement = replacement.at(1); - } - - return !code.empty(); -} - -// LLStringUtil::format recogizes the following patterns. -// All substitutions *must* be encased in []'s in the input string. -// The []'s are optional in the substitution map. -// [FOO_123] -// [FOO,number,precision] -// [FOO,datetime,format] - - -// static -template<> -S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; - S32 res = 0; - - std::string output; - std::vector tokens; - - std::string::size_type start = 0; - std::string::size_type prev_start = 0; - std::string::size_type key_start = 0; - while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) - { - output += std::string(s, prev_start, key_start-prev_start); - prev_start = start; - - bool found_replacement = false; - std::string replacement; - - if (tokens.size() == 0) - { - found_replacement = false; - } - else if (tokens.size() == 1) - { - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - } - else if (tokens[1] == "number") - { - std::string param = "0"; - - if (tokens.size() > 2) param = tokens[2]; - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - if (found_replacement) formatNumber (replacement, param); - } - else if (tokens[1] == "datetime") - { - std::string param; - if (tokens.size() > 2) param = tokens[2]; - - format_map_t::const_iterator iter = substitutions.find("datetime"); - if (iter != substitutions.end()) - { - S32 secFromEpoch = 0; - bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch); - if (r) - { - found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); - } - } - } - - if (found_replacement) - { - output += replacement; - res++; - } - else - { - // we had no replacement, use the string as is - // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); - } - tokens.clear(); - } - // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; - return res; -} - -//static -template<> -S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; - S32 res = 0; - - if (!substitutions.isMap()) - { - return res; - } - - std::string output; - std::vector tokens; - - std::string::size_type start = 0; - std::string::size_type prev_start = 0; - std::string::size_type key_start = 0; - while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) - { - output += std::string(s, prev_start, key_start-prev_start); - prev_start = start; - - bool found_replacement = false; - std::string replacement; - - if (tokens.size() == 0) - { - found_replacement = false; - } - else if (tokens.size() == 1) - { - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - } - else if (tokens[1] == "number") - { - std::string param = "0"; - - if (tokens.size() > 2) param = tokens[2]; - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - if (found_replacement) formatNumber (replacement, param); - } - else if (tokens[1] == "datetime") - { - std::string param; - if (tokens.size() > 2) param = tokens[2]; - - S32 secFromEpoch = (S32) substitutions["datetime"].asInteger(); - found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch); - } - - if (found_replacement) - { - output += replacement; - res++; - } - else - { - // we had no replacement, use the string as is - // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); - } - tokens.clear(); - } - // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; - return res; -} - -//////////////////////////////////////////////////////////// -// Testing - -#ifdef _DEBUG - -template -void LLStringUtilBase::testHarness() -{ - std::string s1; - - llassert( s1.c_str() == NULL ); - llassert( s1.size() == 0 ); - llassert( s1.empty() ); - - std::string s2( "hello"); - llassert( !strcmp( s2.c_str(), "hello" ) ); - llassert( s2.size() == 5 ); - llassert( !s2.empty() ); - std::string s3( s2 ); - - llassert( "hello" == s2 ); - llassert( s2 == "hello" ); - llassert( s2 > "gello" ); - llassert( "gello" < s2 ); - llassert( "gello" != s2 ); - llassert( s2 != "gello" ); - - std::string s4 = s2; - llassert( !s4.empty() ); - s4.empty(); - llassert( s4.empty() ); - - std::string s5(""); - llassert( s5.empty() ); - - llassert( isValidIndex(s5, 0) ); - llassert( !isValidIndex(s5, 1) ); - - s3 = s2; - s4 = "hello again"; - - s4 += "!"; - s4 += s4; - llassert( s4 == "hello again!hello again!" ); - - - std::string s6 = s2 + " " + s2; - std::string s7 = s6; - llassert( s6 == s7 ); - llassert( !( s6 != s7) ); - llassert( !(s6 < s7) ); - llassert( !(s6 > s7) ); - - llassert( !(s6 == "hi")); - llassert( s6 == "hello hello"); - llassert( s6 < "hi"); - - llassert( s6[1] == 'e' ); - s6[1] = 'f'; - llassert( s6[1] == 'f' ); - - s2.erase( 4, 1 ); - llassert( s2 == "hell"); - s2.insert( 0, "y" ); - llassert( s2 == "yhell"); - s2.erase( 1, 3 ); - llassert( s2 == "yl"); - s2.insert( 1, "awn, don't yel"); - llassert( s2 == "yawn, don't yell"); - - std::string s8 = s2.substr( 6, 5 ); - llassert( s8 == "don't" ); - - std::string s9 = " \t\ntest \t\t\n "; - trim(s9); - llassert( s9 == "test" ); - - s8 = "abc123&*(ABC"; - - s9 = s8; - toUpper(s9); - llassert( s9 == "ABC123&*(ABC" ); - - s9 = s8; - toLower(s9); - llassert( s9 == "abc123&*(abc" ); - - - std::string s10( 10, 'x' ); - llassert( s10 == "xxxxxxxxxx" ); - - std::string s11( "monkey in the middle", 7, 2 ); - llassert( s11 == "in" ); - - std::string s12; //empty - s12 += "foo"; - llassert( s12 == "foo" ); - - std::string s13; //empty - s13 += 'f'; - llassert( s13 == "f" ); -} - - -#endif // _DEBUG +/** + * @file llstring.cpp + * @brief String utility functions and the std::string class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llstring.h" +#include "llerror.h" +#include "llfasttimer.h" +#include "llsd.h" +#include + +#if LL_WINDOWS +#include "llwin32headerslean.h" +#include // for WideCharToMultiByte +#endif + +std::string ll_safe_string(const char* in) +{ + if(in) return std::string(in); + return std::string(); +} + +std::string ll_safe_string(const char* in, S32 maxlen) +{ + if(in && maxlen > 0 ) return std::string(in, maxlen); + + return std::string(); +} + +bool is_char_hex(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return true; + } + else if((hex >= 'a') && (hex <='f')) + { + return true; + } + else if((hex >= 'A') && (hex <='F')) + { + return true; + } + return false; // uh - oh, not hex any more... +} + +U8 hex_as_nybble(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return (U8)(hex - '0'); + } + else if((hex >= 'a') && (hex <='f')) + { + return (U8)(10 + hex - 'a'); + } + else if((hex >= 'A') && (hex <='F')) + { + return (U8)(10 + hex - 'A'); + } + return 0; // uh - oh, not hex any more... +} + +bool iswindividual(llwchar elem) +{ + U32 cur_char = (U32)elem; + bool result = false; + if (0x2E80<= cur_char && cur_char <= 0x9FFF) + { + result = true; + } + else if (0xAC00<= cur_char && cur_char <= 0xD7A0 ) + { + result = true; + } + else if (0xF900<= cur_char && cur_char <= 0xFA60 ) + { + result = true; + } + return result; +} + +bool _read_file_into_string(std::string& str, const std::string& filename) +{ + llifstream ifs(filename.c_str(), llifstream::binary); + if (!ifs.is_open()) + { + LL_INFOS() << "Unable to open file " << filename << LL_ENDL; + return false; + } + + std::ostringstream oss; + + oss << ifs.rdbuf(); + str = oss.str(); + ifs.close(); + return true; +} + + + + +// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c +// for the Unicode implementation - this doesn't match because it was written before finding +// it. + + +std::ostream& operator<<(std::ostream &s, const LLWString &wstr) +{ + std::string utf8_str = wstring_to_utf8str(wstr); + s << utf8_str; + return s; +} + +std::string rawstr_to_utf8(const std::string& raw) +{ + LLWString wstr(utf8str_to_wstring(raw)); + return wstring_to_utf8str(wstr); +} + +std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) +{ + U32 cur_char = (U32)in_char; + char* base = outchars; + if (cur_char < 0x80) + { + *outchars++ = (U8)cur_char; + } + else if (cur_char < 0x800) + { + *outchars++ = 0xC0 | (cur_char >> 6); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x10000) + { + *outchars++ = 0xE0 | (cur_char >> 12); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x200000) + { + *outchars++ = 0xF0 | (cur_char >> 18); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x4000000) + { + *outchars++ = 0xF8 | (cur_char >> 24); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x80000000) + { + *outchars++ = 0xFC | (cur_char >> 30); + *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else + { + LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; + *outchars++ = LL_UNKNOWN_CHAR; + } + return outchars - base; +} + +auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) +{ + const U16* base = inchars; + U16 cur_char = *inchars++; + llwchar char32 = cur_char; + if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF)) + { + // Surrogates + char32 = ((llwchar)(cur_char - 0xD800)) << 10; + cur_char = *inchars++; + char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL; + } + else + { + char32 = (llwchar)cur_char; + } + *outchar = char32; + return inchars - base; +} + +llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) +{ + llutf16string out; + + S32 i = 0; + while (i < len) + { + U32 cur_char = utf32str[i]; + if (cur_char > 0xFFFF) + { + out += (0xD7C0 + (cur_char >> 10)); + out += (0xDC00 | (cur_char & 0x3FF)); + } + else + { + out += cur_char; + } + i++; + } + return out; +} + +llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) +{ + LLWString wstr = utf8str_to_wstring ( utf8str, len ); + return wstring_to_utf16str ( wstr ); +} + +LLWString utf16str_to_wstring(const U16* utf16str, size_t len) +{ + LLWString wout; + if (len == 0) return wout; + + S32 i = 0; + const U16* chars16 = utf16str; + while (i < len) + { + llwchar cur_char; + i += utf16chars_to_wchar(chars16+i, &cur_char); + wout += cur_char; + } + return wout; +} + +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) +{ + S32 surrogate_pairs = 0; + // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): + const U16 *const utf16_chars = &(*(utf16str.begin())); + S32 i = 0; + while (i < utf16_len) + { + const U16 c = utf16_chars[i++]; + if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16 + { // Have first byte of a surrogate pair + if (i >= utf16_len) + { + break; + } + const U16 d = utf16_chars[i]; + if (d >= 0xDC00 && d <= 0xDFFF) + { // Have valid second byte of a surrogate pair + surrogate_pairs++; + i++; + } + } + } + return utf16_len - surrogate_pairs; +} + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen) +{ + const S32 end = llmin((S32)wstr.length(), woffset + wlen); + if (end < woffset) + { + return 0; + } + else + { + S32 length = end - woffset; + for (S32 i = woffset; i < end; i++) + { + if (wstr[i] >= 0x10000) + { + length++; + } + } + return length; + } +} + +// Given a wstring and an offset in it, returns the length as wstring (i.e., +// number of llwchars) of the longest substring that starts at the offset +// and whose equivalent utf-16 string does not exceeds the given utf16_length. +S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned) +{ + const auto end = wstr.length(); + bool u{ false }; + S32 n = woffset + utf16_length; + S32 i = woffset; + while (i < end) + { + if (wstr[i] >= 0x10000) + { + --n; + } + if (i >= n) + { + u = (i > n); + break; + } + i++; + } + if (unaligned) + { + *unaligned = u; + } + return i - woffset; +} + +S32 wchar_utf8_length(const llwchar wc) +{ + if (wc < 0x80) + { + return 1; + } + else if (wc < 0x800) + { + return 2; + } + else if (wc < 0x10000) + { + return 3; + } + else if (wc < 0x200000) + { + return 4; + } + else if (wc < 0x4000000) + { + return 5; + } + else + { + return 6; + } +} + +std::string wchar_utf8_preview(const llwchar wc) +{ + std::ostringstream oss; + oss << std::hex << std::uppercase << (U32)wc; + + U8 out_bytes[8]; + U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + + if (size > 1) + { + oss << " ["; + for (U32 i = 0; i < size; ++i) + { + if (i) + { + oss << ", "; + } + oss << (int)out_bytes[i]; + } + oss << "]"; + } + + return oss.str(); +} + +S32 wstring_utf8_length(const LLWString& wstr) +{ + S32 len = 0; + for (S32 i = 0; i < (S32)wstr.length(); i++) + { + len += wchar_utf8_length(wstr[i]); + } + return len; +} + +LLWString utf8str_to_wstring(const char* utf8str, size_t len) +{ + LLWString wout; + + S32 i = 0; + while (i < len) + { + llwchar unichar; + U8 cur_char = utf8str[i]; + + if (cur_char < 0x80) + { + // Ascii character, just add it + unichar = cur_char; + } + else + { + S32 cont_bytes = 0; + if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32 + { + unichar = (0x1F&cur_char); + cont_bytes = 1; + } + else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32 + { + unichar = (0x0F&cur_char); + cont_bytes = 2; + } + else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32 + { + unichar = (0x07&cur_char); + cont_bytes = 3; + } + else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32 + { + unichar = (0x03&cur_char); + cont_bytes = 4; + } + else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32 + { + unichar = (0x01&cur_char); + cont_bytes = 5; + } + else + { + wout += LL_UNKNOWN_CHAR; + ++i; + continue; + } + + // Check that this character doesn't go past the end of the string + auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes); + do + { + ++i; + + cur_char = utf8str[i]; + if ( (cur_char >> 6) == 0x2 ) + { + unichar <<= 6; + unichar += (0x3F&cur_char); + } + else + { + // Malformed sequence - roll back to look at this as a new char + unichar = LL_UNKNOWN_CHAR; + --i; + break; + } + } while(i < end); + + // Handle overlong characters and NULL characters + if ( ((cont_bytes == 1) && (unichar < 0x80)) + || ((cont_bytes == 2) && (unichar < 0x800)) + || ((cont_bytes == 3) && (unichar < 0x10000)) + || ((cont_bytes == 4) && (unichar < 0x200000)) + || ((cont_bytes == 5) && (unichar < 0x4000000)) ) + { + unichar = LL_UNKNOWN_CHAR; + } + } + + wout += unichar; + ++i; + } + return wout; +} + +std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) +{ + std::string out; + + S32 i = 0; + while (i < len) + { + char tchars[8]; /* Flawfinder: ignore */ + auto n = wchar_to_utf8chars(utf32str[i], tchars); + tchars[n] = 0; + out += tchars; + i++; + } + return out; +} + +std::string utf16str_to_utf8str(const U16* utf16str, size_t len) +{ + return wstring_to_utf8str(utf16str_to_wstring(utf16str, len)); +} + +std::string utf8str_trim(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::trim(wstr); + return wstring_to_utf8str(wstr); +} + + +std::string utf8str_tolower(const std::string& utf8str) +{ + LLWString out_str = utf8str_to_wstring(utf8str); + LLWStringUtil::toLower(out_str); + return wstring_to_utf8str(out_str); +} + + +S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs) +{ + LLWString wlhs = utf8str_to_wstring(lhs); + LLWString wrhs = utf8str_to_wstring(rhs); + return LLWStringUtil::compareInsensitive(wlhs, wrhs); +} + +std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) +{ + if (0 == max_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= max_len) + { + return utf8str; + } + else + { + S32 cur_char = max_len; + + // If we're ASCII, we don't need to do anything + if ((U8)utf8str[cur_char] > 0x7f) + { + // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back + // to the first character + while (0x80 == (0xc0 & utf8str[cur_char])) + { + cur_char--; + // Keep moving forward until we hit the first char; + if (cur_char == 0) + { + // Make sure we don't trash memory if we've got a bogus string. + break; + } + } + } + // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars + return utf8str.substr(0, cur_char); + } +} + +std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len) +{ + if (0 == symbol_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= symbol_len) + { + return utf8str; + } + else + { + int len = 0, byteIndex = 0; + const char* aStr = utf8str.c_str(); + size_t origSize = utf8str.size(); + + for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++) + { + if ((aStr[byteIndex] & 0xc0) != 0x80) + { + len += 1; + } + } + return utf8str.substr(0, byteIndex); + } +} + +std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::replaceChar(wstr, target_char, replace_char); + //wstr = wstring_substChar(wstr, target_char, replace_char); + return wstring_to_utf8str(wstr); +} + +std::string utf8str_makeASCII(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::_makeASCII(wstr); + return wstring_to_utf8str(wstr); +} + +std::string mbcsstring_makeASCII(const std::string& wstr) +{ + // Replace non-ASCII chars with replace_char + std::string out_str = wstr; + for (S32 i = 0; i < (S32)out_str.length(); i++) + { + if ((U8)out_str[i] > 0x7f) + { + out_str[i] = LL_UNKNOWN_CHAR; + } + } + return out_str; +} + +std::string utf8str_removeCRLF(const std::string& utf8str) +{ + if (0 == utf8str.length()) + { + return std::string(); + } + const char CR = 13; + + std::string out; + out.reserve(utf8str.length()); + const S32 len = (S32)utf8str.length(); + for( S32 i = 0; i < len; i++ ) + { + if( utf8str[i] != CR ) + { + out.push_back(utf8str[i]); + } + } + return out; +} + +llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) +{ + switch (length) + { + case 2: + return ((utf8str[offset] & 0x1F) << 6) + + (utf8str[offset + 1] & 0x3F); + case 3: + return ((utf8str[offset] & 0x0F) << 12) + + ((utf8str[offset + 1] & 0x3F) << 6) + + (utf8str[offset + 2] & 0x3F); + case 4: + return ((utf8str[offset] & 0x07) << 18) + + ((utf8str[offset + 1] & 0x3F) << 12) + + ((utf8str[offset + 2] & 0x3F) << 6) + + (utf8str[offset + 3] & 0x3F); + case 5: + return ((utf8str[offset] & 0x03) << 24) + + ((utf8str[offset + 1] & 0x3F) << 18) + + ((utf8str[offset + 2] & 0x3F) << 12) + + ((utf8str[offset + 3] & 0x3F) << 6) + + (utf8str[offset + 4] & 0x3F); + case 6: + return ((utf8str[offset] & 0x01) << 30) + + ((utf8str[offset + 1] & 0x3F) << 24) + + ((utf8str[offset + 2] & 0x3F) << 18) + + ((utf8str[offset + 3] & 0x3F) << 12) + + ((utf8str[offset + 4] & 0x3F) << 6) + + (utf8str[offset + 5] & 0x3F); + case 7: + return ((utf8str[offset + 1] & 0x03) << 30) + + ((utf8str[offset + 2] & 0x3F) << 24) + + ((utf8str[offset + 3] & 0x3F) << 18) + + ((utf8str[offset + 4] & 0x3F) << 12) + + ((utf8str[offset + 5] & 0x3F) << 6) + + (utf8str[offset + 6] & 0x3F); + } + return LL_UNKNOWN_CHAR; +} + +std::string utf8str_showBytesUTF8(const std::string& utf8str) +{ + std::string result; + + bool in_sequence = false; + size_t sequence_size = 0; + size_t byte_index = 0; + size_t source_length = utf8str.size(); + + auto open_sequence = [&]() + { + if (!result.empty() && result.back() != '\n') + result += '\n'; // Use LF as a separator before new UTF-8 sequence + result += '['; + in_sequence = true; + }; + + auto close_sequence = [&]() + { + llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); + if (unicode != LL_UNKNOWN_CHAR) + { + result += llformat("+%04X", unicode); + } + result += ']'; + in_sequence = false; + sequence_size = 0; + }; + + while (byte_index < source_length) + { + U8 byte = utf8str[byte_index]; + if (byte >= 0x80) // Part of an UTF-8 sequence + { + if (!in_sequence) // Start new UTF-8 sequence + { + open_sequence(); + } + else if (byte >= 0xC0) // Start another UTF-8 sequence + { + close_sequence(); + open_sequence(); + } + else // Continue the same UTF-8 sequence + { + result += '.'; + } + result += llformat("%02X", byte); // The byte is represented in hexadecimal form + ++sequence_size; + } + else // ASCII symbol is represented as a character + { + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + if (byte != '\n') + { + result += '\n'; // Use LF as a separator between UTF-8 and ASCII + } + } + result += byte; + } + ++byte_index; + } + + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + } + + return result; +} + +// Search for any emoji symbol, return true if found +bool wstring_has_emoji(const LLWString& wstr) +{ + for (const llwchar& wch : wstr) + { + if (LLStringOps::isEmoji(wch)) + return true; + } + + return false; +} + +// Cut emoji symbols if exist +bool wstring_remove_emojis(LLWString& wstr) +{ + bool found = false; + for (size_t i = 0; i < wstr.size(); ++i) + { + if (LLStringOps::isEmoji(wstr[i])) + { + wstr.erase(i--, 1); + found = true; + } + } + return found; +} + +// Cut emoji symbols if exist +bool utf8str_remove_emojis(std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + if (!wstring_remove_emojis(wstr)) + return false; + utf8str = wstring_to_utf8str(wstr); + return true; +} + +#if LL_WINDOWS +unsigned int ll_wstring_default_code_page() +{ + return CP_UTF8; +} + +std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page) +{ + std::string out; + if(in) + { + int len_out = WideCharToMultiByte( + code_page, + 0, + in, + len_in, + NULL, + 0, + 0, + 0); + // We will need two more bytes for the double NULL ending + // created in WideCharToMultiByte(). + char* pout = new char [len_out + 2]; + memset(pout, 0, len_out + 2); + if(pout) + { + WideCharToMultiByte( + code_page, + 0, + in, + len_in, + pout, + len_out, + 0, + 0); + out.assign(pout); + delete[] pout; + } + } + return out; +} + +std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page) +{ + // From review: + // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, + // plus one for a null terminator, and be guaranteed to not overflow. + + // Normally, I'd call that sort of thing premature optimization, + // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. +// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); + + // reserve an output buffer that will be destroyed on exit, with a place + // to put NULL terminator + std::vector w_out(len + 1); + + memset(&w_out[0], 0, w_out.size()); + int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, + &w_out[0], w_out.size() - 1); + + //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. + w_out[real_output_str_len] = 0; + + // construct string from our temporary output buffer + return {&w_out[0]}; +} + +LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len) +{ + // Whether or not std::wstring and llutf16string are distinct types, they + // both hold UTF-16LE characters. (See header file comments.) Pretend this + // wchar_t* sequence is really a U16* sequence and use the conversion we + // define above. + return utf16str_to_wstring(reinterpret_cast(in), len); +} + +std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len) +{ + // first, convert to llutf16string, for which we have a real implementation + auto utf16str{ wstring_to_utf16str(in, len) }; + // then, because each U16 char must be UTF-16LE encoded, pretend the U16* + // string pointer is a wchar_t* and instantiate a std::wstring of the same + // length. + return { reinterpret_cast(utf16str.c_str()), utf16str.length() }; +} + +std::string ll_convert_string_to_utf8_string(const std::string& in) +{ + // If you pass code_page, you must also pass length, otherwise the code + // page parameter will be mistaken for length. + auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP); + // CP_UTF8 is default -- see ll_wstring_default_code_page() above. + return ll_convert_wide_to_string(w_mesg); +} + +namespace +{ + +void HeapFree_deleter(void* ptr) +{ + // instead of LocalFree(), per https://stackoverflow.com/a/31541205 + HeapFree(GetProcessHeap(), NULL, ptr); +} + +} // anonymous namespace + +template<> +std::wstring windows_message(DWORD error) +{ + // derived from https://stackoverflow.com/a/455533 + wchar_t* rawptr = nullptr; + auto okay = FormatMessageW( + // use system message tables for GetLastError() codes + FORMAT_MESSAGE_FROM_SYSTEM | + // internally allocate buffer and return its pointer + FORMAT_MESSAGE_ALLOCATE_BUFFER | + // you cannot pass insertion parameters (thanks Gandalf) + FORMAT_MESSAGE_IGNORE_INSERTS | + // ignore line breaks in message definition text + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM + error, // dwMessageId + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId + (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t* + 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER + NULL); // Arguments, unused + + // make a unique_ptr from rawptr so it gets cleaned up properly + std::unique_ptr bufferptr(rawptr, HeapFree_deleter); + + if (okay && bufferptr) + { + // got the message, return it ('okay' is length in characters) + return { bufferptr.get(), okay }; + } + + // did not get the message, synthesize one + auto format_message_error = GetLastError(); + std::wostringstream out; + out << L"GetLastError() " << error << L" (FormatMessageW() failed with " + << format_message_error << L")"; + return out.str(); +} + +std::optional llstring_getoptenv(const std::string& key) +{ + auto wkey = ll_convert_string_to_wide(key); + // Take a wild guess as to how big the buffer should be. + std::vector buffer(1024); + auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + // If our initial guess was too short, n will indicate the size (in + // wchar_t's) that buffer should have been, including the terminating nul. + if (n > (buffer.size() - 1)) + { + // make it big enough + buffer.resize(n); + // and try again + n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + } + // did that (ultimately) succeed? + if (n) + { + // great, return populated std::optional + return std::make_optional(&buffer[0]); + } + + // not successful + auto last_error = GetLastError(); + // Don't bother warning for NOT_FOUND; that's an expected case + if (last_error != ERROR_ENVVAR_NOT_FOUND) + { + LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " + << windows_message(last_error) << LL_ENDL; + } + // return empty std::optional + return {}; +} + +#else // ! LL_WINDOWS + +std::optional llstring_getoptenv(const std::string& key) +{ + auto found = getenv(key.c_str()); + if (found) + { + // return populated std::optional + return std::make_optional(found); + } + else + { + // return empty std::optional + return {}; + } +} + +#endif // ! LL_WINDOWS + +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0; +std::map LLStringOps::datetimeToCodes; + +std::vector LLStringOps::sWeekDayList; +std::vector LLStringOps::sWeekDayShortList; +std::vector LLStringOps::sMonthList; +std::vector LLStringOps::sMonthShortList; + + +std::string LLStringOps::sDayFormat; +std::string LLStringOps::sAM; +std::string LLStringOps::sPM; + +// static +bool LLStringOps::isEmoji(llwchar a) +{ +#if 0 // Do not consider special characters that might have a corresponding + // glyph in the monochorme fallback fonts as a "genuine" emoji. HB + return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) || + (a >= 0x1f000 && a < 0x20000); +#else + // These are indeed "genuine" emojis, we *do want* rendered as such. HB + return a >= 0x1f000 && a < 0x20000; +#endif + } + +S32 LLStringOps::collate(const llwchar* a, const llwchar* b) +{ + #if LL_WINDOWS + // in Windows, wide string functions operator on 16-bit strings, + // not the proper 32 bit wide string + return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); + #else + return wcscoll(a, b); + #endif +} + +void LLStringOps::setupDatetimeInfo (bool daylight) +{ + time_t nowT, localT, gmtT; + struct tm * tmpT; + + nowT = time (NULL); + + tmpT = gmtime (&nowT); + gmtT = mktime (tmpT); + + tmpT = localtime (&nowT); + localT = mktime (tmpT); + + sLocalTimeOffset = (long) (gmtT - localT); + if (tmpT->tm_isdst) + { + sLocalTimeOffset -= 60 * 60; // 1 hour + } + + sPacificDaylightTime = daylight; + sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; + + datetimeToCodes["wkday"] = "%a"; // Thu + datetimeToCodes["weekday"] = "%A"; // Thursday + datetimeToCodes["year4"] = "%Y"; // 2009 + datetimeToCodes["year"] = "%Y"; // 2009 + datetimeToCodes["year2"] = "%y"; // 09 + datetimeToCodes["mth"] = "%b"; // Aug + datetimeToCodes["month"] = "%B"; // August + datetimeToCodes["mthnum"] = "%m"; // 08 + datetimeToCodes["day"] = "%d"; // 31 + datetimeToCodes["sday"] = "%-d"; // 9 + datetimeToCodes["hour24"] = "%H"; // 14 + datetimeToCodes["hour"] = "%H"; // 14 + datetimeToCodes["hour12"] = "%I"; // 02 + datetimeToCodes["min"] = "%M"; // 59 + datetimeToCodes["ampm"] = "%p"; // AM + datetimeToCodes["second"] = "%S"; // 59 + datetimeToCodes["timezone"] = "%Z"; // PST +} + +void tokenizeStringToArray(const std::string& data, std::vector& output) +{ + output.clear(); + size_t length = data.size(); + + // tokenize it and put it in the array + std::string cur_word; + for(size_t i = 0; i < length; ++i) + { + if(data[i] == ':') + { + output.push_back(cur_word); + cur_word.clear(); + } + else + { + cur_word.append(1, data[i]); + } + } + output.push_back(cur_word); +} + +void LLStringOps::setupWeekDaysNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayList); +} +void LLStringOps::setupWeekDaysShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayShortList); +} +void LLStringOps::setupMonthNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthList); +} +void LLStringOps::setupMonthShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthShortList); +} +void LLStringOps::setupDayFormat(const std::string& data) +{ + sDayFormat = data; +} + + +std::string LLStringOps::getDatetimeCode (std::string key) +{ + std::map::iterator iter; + + iter = datetimeToCodes.find (key); + if (iter != datetimeToCodes.end()) + { + return iter->second; + } + else + { + return std::string(""); + } +} + +std::string LLStringOps::getReadableNumber(F64 num) +{ + if (fabs(num)>=1e9) + { + return llformat("%.2lfB", num / 1e9); + } + else if (fabs(num)>=1e6) + { + return llformat("%.2lfM", num / 1e6); + } + else if (fabs(num)>=1e3) + { + return llformat("%.2lfK", num / 1e3); + } + else + { + return llformat("%.2lf", num); + } +} + +namespace LLStringFn +{ + // NOTE - this restricts output to ascii + void replace_nonprintable_in_ascii(std::basic_string& string, char replacement) + { + const char MIN = 0x20; + std::basic_string::size_type len = string.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if(string[ii] < MIN) + { + string[ii] = replacement; + } + } + } + + + // NOTE - this restricts output to ascii + void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, + char replacement) + { + const char MIN = 0x20; + const char PIPE = 0x7c; + std::basic_string::size_type len = str.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + if( (str[ii] < MIN) || (str[ii] == PIPE) ) + { + str[ii] = replacement; + } + } + } + + // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on + // allowable code points for XML. Specifically, they are: + // 0x09, 0x0a, 0x0d, and 0x20 on up. JC + std::string strip_invalid_xml(const std::string& instr) + { + std::string output; + output.reserve( instr.size() ); + std::string::const_iterator it = instr.begin(); + while (it != instr.end()) + { + // Must compare as unsigned for >= + // Test most likely match first + const unsigned char c = (unsigned char)*it; + if ( c >= (unsigned char)0x20 // SPACE + || c == (unsigned char)0x09 // TAB + || c == (unsigned char)0x0a // LINE_FEED + || c == (unsigned char)0x0d ) // CARRIAGE_RETURN + { + output.push_back(c); + } + ++it; + } + return output; + } + + /** + * @brief Replace all control characters (c < 0x20) with replacement in + * string. + */ + void replace_ascii_controlchars(std::basic_string& string, char replacement) + { + const unsigned char MIN = 0x20; + std::basic_string::size_type len = string.size(); + for(std::basic_string::size_type ii = 0; ii < len; ++ii) + { + const unsigned char c = (unsigned char) string[ii]; + if(c < MIN) + { + string[ii] = replacement; + } + } + } +} + +//////////////////////////////////////////////////////////// + +// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime. +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); + +//static +template<> +void LLStringUtil::getTokens(const std::string& instr, std::vector& tokens, const std::string& delims) +{ + // Starting at offset 0, scan forward for the next non-delimiter. We're + // done when the only characters left in 'instr' are delimiters. + for (std::string::size_type begIdx, endIdx = 0; + (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; ) + { + // Found a non-delimiter. After that, find the next delimiter. + endIdx = instr.find_first_of (delims, begIdx); + if (endIdx == std::string::npos) + { + // No more delimiters: this token extends to the end of the string. + endIdx = instr.length(); + } + + // extract the token between begIdx and endIdx; substr() needs length + std::string currToken(instr.substr(begIdx, endIdx - begIdx)); + LLStringUtil::trim (currToken); + tokens.push_back(currToken); + // next scan past delimiters starts at endIdx + } +} + +template<> +LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector& tokens) +{ + const std::string delims (","); + + // Find the first [ + size_type pos1 = instr.find('[', start); + if (pos1 == std::string::npos) + return std::string::npos; + + //Find the first ] after the initial [ + size_type pos2 = instr.find(']', pos1); + if (pos2 == std::string::npos) + return std::string::npos; + + // Find the last [ before ] in case of nested [[]] + pos1 = instr.find_last_of('[', pos2-1); + if (pos1 == std::string::npos || pos1 < start) + return std::string::npos; + + getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims); + start = pos2+1; + + return pos1; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + format_map_t::const_iterator iter = substitutions.find(token); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + // if not, see if there's one WITH brackets + iter = substitutions.find(std::string("[" + token + "]")); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + + return false; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + if (substitutions.has(token)) + { + replacement = substitutions[token].asString(); + return true; + } + // if not, see if there's one WITH brackets + else if (substitutions.has(std::string("[" + token + "]"))) + { + replacement = substitutions[std::string("[" + token + "]")].asString(); + return true; + } + + return false; +} + +//static +template<> +void LLStringUtil::setLocale(std::string inLocale) +{ + sLocale = inLocale; +}; + +//static +template<> +std::string LLStringUtil::getLocale(void) +{ + return sLocale; +}; + +// static +template<> +void LLStringUtil::formatNumber(std::string& numStr, std::string decimals) +{ + std::stringstream strStream; + S32 intDecimals = 0; + + convertToS32 (decimals, intDecimals); + if (!sLocale.empty()) + { + // std::locale() throws if the locale is unknown! (EXT-7926) + try + { + strStream.imbue(std::locale(sLocale.c_str())); + } catch (const std::exception &) + { + LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL; + } + } + + if (!intDecimals) + { + S32 intStr; + + if (convertToS32(numStr, intStr)) + { + strStream << intStr; + numStr = strStream.str(); + } + } + else + { + F32 floatStr; + + if (convertToF32(numStr, floatStr)) + { + strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr; + numStr = strStream.str(); + } + } +} + +// static +template<> +bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, + std::string param, S32 secFromEpoch) +{ + if (param == "local") // local + { + secFromEpoch -= LLStringOps::getLocalTimeOffset(); + } + else if (param != "utc") // slt + { + secFromEpoch -= LLStringOps::getPacificTimeOffset(); + } + + // if never fell into those two ifs above, param must be utc + if (secFromEpoch < 0) secFromEpoch = 0; + + LLDate datetime((F64)secFromEpoch); + std::string code = LLStringOps::getDatetimeCode (token); + + // special case to handle timezone + if (code == "%Z") { + if (param == "utc") + { + replacement = "GMT"; + } + else if (param == "local") + { + replacement = ""; // user knows their own timezone + } + else + { +#if 0 + // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09 + // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT + // "slt" = Second Life Time, which is deprecated. + // If not utc or user local time, fallback to Pacific time + replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; +#else + // SL-20370 : Steeltoe Linden : 29/Sep/23 + // Change "PDT" to "SLT" on menu bar + replacement = "SLT"; +#endif + } + return true; + } + + //EXT-7013 + //few codes are not suppotred by strtime function (example - weekdays for Japanise) + //so use predefined ones + + //if sWeekDayList is not empty than current locale doesn't support + //weekday name. + time_t loc_seconds = (time_t) secFromEpoch; + if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; + } + else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; + } + else if(LLStringOps::sMonthList.size() == 12 && code == "%B") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sMonthList[gmt->tm_mon]; + } + else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) + { + struct tm * gmt = gmtime (&loc_seconds); + LLStringUtil::format_map_t args; + args["[MDAY]"] = llformat ("%d", gmt->tm_mday); + replacement = LLStringOps::sDayFormat; + LLStringUtil::format(replacement, args); + } + else if (code == "%-d") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero + } + else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) + { + struct tm * gmt = gmtime (&loc_seconds); + if(gmt->tm_hour<12) + { + replacement = LLStringOps::sAM; + } + else + { + replacement = LLStringOps::sPM; + } + } + else + { + replacement = datetime.toHTTPDateString(code); + } + + // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format + // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). + // We could have used '%l' format instead, but it's not supported by Windows. + if(code == "%I" && token == "hour12" && replacement.at(0) == '0') + { + replacement = replacement.at(1); + } + + return !code.empty(); +} + +// LLStringUtil::format recogizes the following patterns. +// All substitutions *must* be encased in []'s in the input string. +// The []'s are optional in the substitution map. +// [FOO_123] +// [FOO,number,precision] +// [FOO,datetime,format] + + +// static +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; + S32 res = 0; + + std::string output; + std::vector tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + format_map_t::const_iterator iter = substitutions.find("datetime"); + if (iter != substitutions.end()) + { + S32 secFromEpoch = 0; + bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch); + if (r) + { + found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); + } + } + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} + +//static +template<> +S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; + S32 res = 0; + + if (!substitutions.isMap()) + { + return res; + } + + std::string output; + std::vector tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + S32 secFromEpoch = (S32) substitutions["datetime"].asInteger(); + found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch); + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} + +//////////////////////////////////////////////////////////// +// Testing + +#ifdef _DEBUG + +template +void LLStringUtilBase::testHarness() +{ + std::string s1; + + llassert( s1.c_str() == NULL ); + llassert( s1.size() == 0 ); + llassert( s1.empty() ); + + std::string s2( "hello"); + llassert( !strcmp( s2.c_str(), "hello" ) ); + llassert( s2.size() == 5 ); + llassert( !s2.empty() ); + std::string s3( s2 ); + + llassert( "hello" == s2 ); + llassert( s2 == "hello" ); + llassert( s2 > "gello" ); + llassert( "gello" < s2 ); + llassert( "gello" != s2 ); + llassert( s2 != "gello" ); + + std::string s4 = s2; + llassert( !s4.empty() ); + s4.empty(); + llassert( s4.empty() ); + + std::string s5(""); + llassert( s5.empty() ); + + llassert( isValidIndex(s5, 0) ); + llassert( !isValidIndex(s5, 1) ); + + s3 = s2; + s4 = "hello again"; + + s4 += "!"; + s4 += s4; + llassert( s4 == "hello again!hello again!" ); + + + std::string s6 = s2 + " " + s2; + std::string s7 = s6; + llassert( s6 == s7 ); + llassert( !( s6 != s7) ); + llassert( !(s6 < s7) ); + llassert( !(s6 > s7) ); + + llassert( !(s6 == "hi")); + llassert( s6 == "hello hello"); + llassert( s6 < "hi"); + + llassert( s6[1] == 'e' ); + s6[1] = 'f'; + llassert( s6[1] == 'f' ); + + s2.erase( 4, 1 ); + llassert( s2 == "hell"); + s2.insert( 0, "y" ); + llassert( s2 == "yhell"); + s2.erase( 1, 3 ); + llassert( s2 == "yl"); + s2.insert( 1, "awn, don't yel"); + llassert( s2 == "yawn, don't yell"); + + std::string s8 = s2.substr( 6, 5 ); + llassert( s8 == "don't" ); + + std::string s9 = " \t\ntest \t\t\n "; + trim(s9); + llassert( s9 == "test" ); + + s8 = "abc123&*(ABC"; + + s9 = s8; + toUpper(s9); + llassert( s9 == "ABC123&*(ABC" ); + + s9 = s8; + toLower(s9); + llassert( s9 == "abc123&*(abc" ); + + + std::string s10( 10, 'x' ); + llassert( s10 == "xxxxxxxxxx" ); + + std::string s11( "monkey in the middle", 7, 2 ); + llassert( s11 == "in" ); + + std::string s12; //empty + s12 += "foo"; + llassert( s12 == "foo" ); + + std::string s13; //empty + s13 += 'f'; + llassert( s13 == "f" ); +} + + +#endif // _DEBUG diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index ac05dc3cd0..61d698687a 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1,2039 +1,2039 @@ -/** - * @file llstring.h - * @brief String utility functions and std::string class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSTRING_H -#define LL_LLSTRING_H - -#include -#include -#include -#include -#include -#include // std::wcslen() -//#include -#include -#include -#include -#include -#include "llformat.h" - -#if LL_LINUX -#include -#include -#endif - -#include -#include - -const char LL_UNKNOWN_CHAR = '?'; -class LLSD; - -#if LL_DARWIN || LL_LINUX -// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already) -#include - -namespace std -{ -template<> -struct char_traits -{ - typedef U16 char_type; - typedef int int_type; - typedef streampos pos_type; - typedef streamoff off_type; - typedef mbstate_t state_type; - - static void - assign(char_type& __c1, const char_type& __c2) - { __c1 = __c2; } - - static bool - eq(const char_type& __c1, const char_type& __c2) - { return __c1 == __c2; } - - static bool - lt(const char_type& __c1, const char_type& __c2) - { return __c1 < __c2; } - - static int - compare(const char_type* __s1, const char_type* __s2, size_t __n) - { return memcmp(__s1, __s2, __n * sizeof(char_type)); } - - static size_t - length(const char_type* __s) - { - const char_type *cur_char = __s; - while (*cur_char != 0) - { - ++cur_char; - } - return cur_char - __s; - } - - static const char_type* - find(const char_type* __s, size_t __n, const char_type& __a) - { return static_cast(memchr(__s, __a, __n * sizeof(char_type))); } - - static char_type* - move(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast(memmove(__s1, __s2, __n * sizeof(char_type))); } - - static char_type* - copy(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */ - - static char_type* - assign(char_type* __s, size_t __n, char_type __a) - { - // This isn't right. - //return static_cast(memset(__s, __a, __n * sizeof(char_type))); - - // I don't think there's a standard 'memset' for 16-bit values. - // Do this the old-fashioned way. - - size_t __i; - for(__i = 0; __i < __n; __i++) - { - __s[__i] = __a; - } - return __s; - } - - static char_type - to_char_type(const int_type& __c) - { return static_cast(__c); } - - static int_type - to_int_type(const char_type& __c) - { return static_cast(__c); } - - static bool - eq_int_type(const int_type& __c1, const int_type& __c2) - { return __c1 == __c2; } - - static int_type - eof() { return static_cast(EOF); } - - static int_type - not_eof(const int_type& __c) - { return (__c == eof()) ? 0 : __c; } - }; -}; -#endif - -class LL_COMMON_API LLStringOps -{ -private: - static long sPacificTimeOffset; - static long sLocalTimeOffset; - static bool sPacificDaylightTime; - - static std::map datetimeToCodes; - -public: - static std::vector sWeekDayList; - static std::vector sWeekDayShortList; - static std::vector sMonthList; - static std::vector sMonthShortList; - static std::string sDayFormat; - - static std::string sAM; - static std::string sPM; - - static char toUpper(char elem) { return toupper((unsigned char)elem); } - static llwchar toUpper(llwchar elem) { return towupper(elem); } - - static char toLower(char elem) { return tolower((unsigned char)elem); } - static llwchar toLower(llwchar elem) { return towlower(elem); } - - static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; } - static bool isSpace(llwchar elem) { return iswspace(elem) != 0; } - - static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; } - static bool isUpper(llwchar elem) { return iswupper(elem) != 0; } - - static bool isLower(char elem) { return islower((unsigned char)elem) != 0; } - static bool isLower(llwchar elem) { return iswlower(elem) != 0; } - - static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; } - static bool isDigit(llwchar a) { return iswdigit(a) != 0; } - - static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; } - static bool isPunct(llwchar a) { return iswpunct(a) != 0; } - - static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; } - static bool isAlpha(llwchar a) { return iswalpha(a) != 0; } - - static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; } - static bool isAlnum(llwchar a) { return iswalnum(a) != 0; } - - // Returns true when 'a' corresponds to a "genuine" emoji. HB - static bool isEmoji(llwchar a); - - static S32 collate(const char* a, const char* b) { return strcoll(a, b); } - static S32 collate(const llwchar* a, const llwchar* b); - - static void setupDatetimeInfo(bool pacific_daylight_time); - - static void setupWeekDaysNames(const std::string& data); - static void setupWeekDaysShortNames(const std::string& data); - static void setupMonthNames(const std::string& data); - static void setupMonthShortNames(const std::string& data); - static void setupDayFormat(const std::string& data); - - - static long getPacificTimeOffset(void) { return sPacificTimeOffset;} - static long getLocalTimeOffset(void) { return sLocalTimeOffset;} - // Is the Pacific time zone (aka server time zone) - // currently in daylight savings time? - static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} - - static std::string getDatetimeCode (std::string key); - - // Express a value like 1234567 as "1.23M" - static std::string getReadableNumber(F64 num); -}; - -/** - * @brief Return a string constructed from in without crashing if the - * pointer is NULL. - */ -LL_COMMON_API std::string ll_safe_string(const char* in); -LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); - - -// Allowing assignments from non-strings into format_map_t is apparently -// *really* error-prone, so subclass std::string with just basic c'tors. -class LLFormatMapString -{ -public: - LLFormatMapString() {}; - LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {}; - LLFormatMapString(const std::string& s) : mString(s) {}; - operator std::string() const { return mString; } - bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; } - std::size_t length() const { return mString.length(); } - -private: - std::string mString; -}; - -template -class LLStringUtilBase -{ -private: - static std::string sLocale; - -public: - typedef std::basic_string string_type; - typedef typename string_type::size_type size_type; - -public: - ///////////////////////////////////////////////////////////////////////////////////////// - // Static Utility functions that operate on std::strings - - static const string_type null; - - typedef std::map format_map_t; - /// considers any sequence of delims as a single field separator - LL_COMMON_API static void getTokens(const string_type& instr, - std::vector& tokens, - const string_type& delims); - /// like simple scan overload, but returns scanned vector - static std::vector getTokens(const string_type& instr, - const string_type& delims); - /// add support for keep_delims and quotes (either could be empty string) - static void getTokens(const string_type& instr, - std::vector& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); - /// like keep_delims-and-quotes overload, but returns scanned vector - static std::vector getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); - /// add support for escapes (could be empty string) - static void getTokens(const string_type& instr, - std::vector& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); - /// like escapes overload, but returns scanned vector - static std::vector getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); - - LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); - LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); - LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); - LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions); - LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions); - LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions); - LL_COMMON_API static void setLocale (std::string inLocale); - LL_COMMON_API static std::string getLocale (void); - - static bool isValidIndex(const string_type& string, size_type i) - { - return !string.empty() && (0 <= i) && (i <= string.size()); - } - - static bool contains(const string_type& string, T c, size_type i=0) - { - return string.find(c, i) != string_type::npos; - } - - static void trimHead(string_type& string); - static void trimTail(string_type& string); - static void trim(string_type& string) { trimHead(string); trimTail(string); } - static void truncate(string_type& string, size_type count); - - static void toUpper(string_type& string); - static void toLower(string_type& string); - - // True if this is the head of s. - static bool isHead( const string_type& string, const T* s ); - - /** - * @brief Returns true if string starts with substr - * - * If etither string or substr are empty, this method returns false. - */ - static bool startsWith( - const string_type& string, - const string_type& substr); - - /** - * @brief Returns true if string ends in substr - * - * If etither string or substr are empty, this method returns false. - */ - static bool endsWith( - const string_type& string, - const string_type& substr); - - /** - * get environment string value with proper Unicode handling - * (key is always UTF-8) - * detect absence by return value == dflt - */ - static string_type getenv(const std::string& key, const string_type& dflt=""); - /** - * get optional environment string value with proper Unicode handling - * (key is always UTF-8) - * detect absence by (! return value) - */ - static std::optional getoptenv(const std::string& key); - - static void addCRLF(string_type& string); - static void removeCRLF(string_type& string); - static void removeWindowsCR(string_type& string); - - static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); - static void replaceNonstandardASCII( string_type& string, T replacement ); - static void replaceChar( string_type& string, T target, T replacement ); - static void replaceString( string_type& string, string_type target, string_type replacement ); - static string_type capitalize(const string_type& str); - static void capitalize(string_type& str); - - static bool containsNonprintable(const string_type& string); - static void stripNonprintable(string_type& string); - - /** - * Double-quote an argument string if needed, unless it's already - * double-quoted. Decide whether it's needed based on the presence of any - * character in @a triggers (default space or double-quote). If we quote - * it, escape any embedded double-quote with the @a escape string (default - * backslash). - * - * Passing triggers="" means always quote, unless it's already double-quoted. - */ - static string_type quote(const string_type& str, - const string_type& triggers=" \"", - const string_type& escape="\\"); - - /** - * @brief Unsafe way to make ascii characters. You should probably - * only call this when interacting with the host operating system. - * The 1 byte std::string does not work correctly. - * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII - * should work. - */ - static void _makeASCII(string_type& string); - - // Conversion to other data types - static bool convertToBOOL(const string_type& string, bool& value); - static bool convertToU8(const string_type& string, U8& value); - static bool convertToS8(const string_type& string, S8& value); - static bool convertToS16(const string_type& string, S16& value); - static bool convertToU16(const string_type& string, U16& value); - static bool convertToU32(const string_type& string, U32& value); - static bool convertToS32(const string_type& string, S32& value); - static bool convertToF32(const string_type& string, F32& value); - static bool convertToF64(const string_type& string, F64& value); - - ///////////////////////////////////////////////////////////////////////////////////////// - // Utility functions for working with char*'s and strings - - // Like strcmp but also handles empty strings. Uses - // current locale. - static S32 compareStrings(const T* lhs, const T* rhs); - static S32 compareStrings(const string_type& lhs, const string_type& rhs); - - // case insensitive version of above. Uses current locale on - // Win32, and falls back to a non-locale aware comparison on - // Linux. - static S32 compareInsensitive(const T* lhs, const T* rhs); - static S32 compareInsensitive(const string_type& lhs, const string_type& rhs); - - // Case sensitive comparison with good handling of numbers. Does not use current locale. - // a.k.a. strdictcmp() - static S32 compareDict(const string_type& a, const string_type& b); - - // Case *in*sensitive comparison with good handling of numbers. Does not use current locale. - // a.k.a. strdictcmp() - static S32 compareDictInsensitive(const string_type& a, const string_type& b); - - // Puts compareDict() in a form appropriate for LL container classes to use for sorting. - static bool precedesDict( const string_type& a, const string_type& b ); - - // A replacement for strncpy. - // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds - // up to dst_size-1 characters of src. - static void copy(T* dst, const T* src, size_type dst_size); - - // Copies src into dst at a given offset. - static void copyInto(string_type& dst, const string_type& src, size_type offset); - - static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } - - -#ifdef _DEBUG - LL_COMMON_API static void testHarness(); -#endif - -private: - LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector& tokens); -}; - -template const std::basic_string LLStringUtilBase::null; -template std::string LLStringUtilBase::sLocale; - -typedef LLStringUtilBase LLStringUtil; -typedef LLStringUtilBase LLWStringUtil; -typedef std::basic_string LLWString; - -//@ Use this where we want to disallow input in the form of "foo" -// This is used to catch places where english text is embedded in the code -// instead of in a translatable XUI file. -class LLStringExplicit : public std::string -{ -public: - explicit LLStringExplicit(const char* s) : std::string(s) {} - LLStringExplicit(const std::string& s) : std::string(s) {} - LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {} -}; - -struct LLDictionaryLess -{ -public: - bool operator()(const std::string& a, const std::string& b) const - { - return (LLStringUtil::precedesDict(a, b)); - } -}; - - -/** - * Simple support functions - */ - -/** - * @brief chop off the trailing characters in a string. - * - * This function works on bytes rather than glyphs, so this will - * incorrectly truncate non-single byte strings. - * Use utf8str_truncate() for utf8 strings - * @return a copy of in string minus the trailing count bytes. - */ -inline std::string chop_tail_copy( - const std::string& in, - std::string::size_type count) -{ - return std::string(in, 0, in.length() - count); -} - -/** - * @brief This translates a nybble stored as a hex value from 0-f back - * to a nybble in the low order bits of the return byte. - */ -LL_COMMON_API bool is_char_hex(char hex); -LL_COMMON_API U8 hex_as_nybble(char hex); - -/** - * @brief read the contents of a file into a string. - * - * Since this function has no concept of character encoding, most - * anything you do with this method ill-advised. Please avoid. - * @param str [out] The string which will have. - * @param filename The full name of the file to read. - * @return Returns true on success. If false, str is unmodified. - */ -LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); -LL_COMMON_API bool iswindividual(llwchar elem); - -/** - * Unicode support - */ - -/// generic conversion aliases -template -struct ll_convert_impl -{ - // Don't even provide a generic implementation. We specialize for every - // combination we do support. - TO operator()(const FROM& in) const; -}; - -// Use a function template to get the nice ll_convert(from_value) API. -template -TO ll_convert(const FROM& in) -{ - return ll_convert_impl()(in); -} - -// degenerate case -template -struct ll_convert_impl -{ - T operator()(const T& in) const { return in; } -}; - -// simple construction from char* -template -struct ll_convert_impl -{ - T operator()(const typename T::value_type* in) const { return { in }; } -}; - -// specialize ll_convert_impl to return EXPR -#define ll_convert_alias(TO, FROM, EXPR) \ -template<> \ -struct ll_convert_impl \ -{ \ - /* param_type optimally passes both char* and string */ \ - TO operator()(typename boost::call_traits::param_type in) const { return EXPR; } \ -} - -// If all we're doing is copying characters, pass this to ll_convert_alias as -// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl -// specialization above, it implies TO{ in.begin(), in.end() }. -#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() } - -// Generic name for strlen() / wcslen() - the default implementation should -// (!) work with U16 and llwchar, but we don't intend to engage it. -template -size_t ll_convert_length(const CHARTYPE* zstr) -{ - const CHARTYPE* zp; - // classic C string scan - for (zp = zstr; *zp; ++zp) - ; - return (zp - zstr); -} - -// specialize where we have a library function; may use intrinsic operations -template <> -inline size_t ll_convert_length(const wchar_t* zstr) { return std::wcslen(zstr); } -template <> -inline size_t ll_convert_length (const char* zstr) { return std::strlen(zstr); } - -// ll_convert_forms() is short for a bunch of boilerplate. It defines -// longname(const char*, len), longname(const char*), longname(const string&) -// and longname(const string&, len) so calls written pre-ll_convert() will -// work. Most of these overloads will be unified once we turn on C++17 and can -// use std::string_view. -// It also uses aliasmacro to ensure that both ll_convert(const char*) -// and ll_convert(const string&) will work. -#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ -LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ -inline auto longname(const INSTR& in, size_t len) \ -{ \ - return longname(in.c_str(), len); \ -} \ -inline auto longname(const INSTR::value_type* in) \ -{ \ - return longname(in, ll_convert_length(in)); \ -} \ -inline auto longname(const INSTR& in) \ -{ \ - return longname(in.c_str(), in.length()); \ -} \ -/* string param */ \ -aliasmacro(OUTSTR, INSTR, longname(in)); \ -/* char* param */ \ -aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) - -// Make the incoming string a utf8 string. Replaces any unknown glyph -// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest -// of the data may not be recovered. -LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); - -// -// We should never use UTF16 except when communicating with Win32! -// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t -// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because -// the Windows APIs we want to call are all defined in terms of wchar_t* -// (or worse, LPCTSTR). -// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types - -// While there is no point coding for an ASCII-only world (! defined(UNICODE)), -// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going -// forward, we should code in terms of wchar_t and std::wstring so as to -// support either setting of /Zc:wchar_t. - -// The first link above states that char can be used to hold ASCII or any -// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t -// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: -// * char and std::string always hold UTF-8 (of which ASCII is a subset). It -// is a BUG if they are used to pass strings in any other multi-byte -// encoding. -// * wchar_t and std::wstring should be our interface to Windows wide-string -// APIs, and therefore hold UTF-16LE. -// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do -// not introduce new uses of U16 or llutf16string for string data. -// * llwchar and LLWString hold UTF-32 strings. -// * Do not introduce char16_t or std::u16string. -// * Do not introduce char32_t or std::u32string. -// -// This typedef may or may not be identical to std::wstring, depending on -// LL_WCHAR_T_NATIVE. -typedef std::basic_string llutf16string; - -// Considering wchar_t, llwchar and U16, there are three relevant cases: -#if LLWCHAR_IS_WCHAR_T // every which way but Windows -// llwchar is identical to wchar_t, LLWString is identical to std::wstring. -// U16 is distinct, llutf16string is distinct (though pretty useless). -// Given conversions to/from LLWString and to/from llutf16string, conversions -// involving std::wstring would collide. -#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing -// but we can define conversions involving llutf16string without collisions -#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) - -#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t -// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types. -// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string -// can all be defined. -#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) -#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) - -#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t- -// wchar_t is identical to U16, std::wstring is identical to llutf16string. -// Given conversions to/from LLWString and to/from std::wstring, conversions -// involving llutf16string would collide. -#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing -// but we can define conversions involving std::wstring without collisions -#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) -#endif - -ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring); -ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str); -ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str); -ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring); - -// Same function, better name. JC -inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } - -LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); - -ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); -ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); - -// an older alias for utf16str_to_utf8str(llutf16string) -inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} - -// Length of this UTF32 string in bytes when transformed to UTF8 -LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); - -// Length in bytes of this wide char in a UTF8 string -LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); - -LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc); - -LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); - -// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); - -// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); - -// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr); - -/** - * @brief Properly truncate a utf8 string to a maximum byte count. - * - * The returned string may be less than max_len if the truncation - * happens in the middle of a glyph. If max_len is longer than the - * string passed in, the return value == utf8str. - * @param utf8str A valid utf8 string to truncate. - * @param max_len The maximum number of bytes in the return value. - * @return Returns a valid utf8 string with byte count <= max_len. - */ -LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); - -LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); - -LL_COMMON_API S32 utf8str_compare_insensitive( - const std::string& lhs, - const std::string& rhs); - -/** -* @brief Properly truncate a utf8 string to a maximum character count. -* -* If symbol_len is longer than the string passed in, the return -* value == utf8str. -* @param utf8str A valid utf8 string to truncate. -* @param symbol_len The maximum number of symbols in the return value. -* @return Returns a valid utf8 string with symbol count <= max_len. -*/ -LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len); - -/** - * @brief Replace all occurences of target_char with replace_char - * - * @param utf8str A utf8 string to process. - * @param target_char The wchar to be replaced - * @param replace_char The wchar which is written on replace - */ -LL_COMMON_API std::string utf8str_substChar( - const std::string& utf8str, - const llwchar target_char, - const llwchar replace_char); - -LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); - -// Hack - used for evil notecards. -LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); - -LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); - -LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); - -LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); - -LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr); - -LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr); - -LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str); - -#if LL_WINDOWS -/* @name Windows string helpers - */ -//@{ - -/** - * @brief Convert a wide string to/from std::string - * Convert a Windows wide string to/from our LLWString - * - * This replaces the unsafe W2A macro from ATL. - */ -// Avoid requiring this header to #include the Windows header file declaring -// our actual default code_page by delegating this function to our .cpp file. -LL_COMMON_API unsigned int ll_wstring_default_code_page(); - -// This is like ll_convert_forms(), with the added complexity of a code page -// parameter that may or may not be passed. -#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \ -/* declare the only nontrivial implementation (in .cpp file) */ \ -LL_COMMON_API OUTSTR longname( \ - const INSTR::value_type* in, \ - size_t len, \ - unsigned int code_page=ll_wstring_default_code_page()); \ -/* if passed only a char pointer, scan for nul terminator */ \ -inline auto longname(const INSTR::value_type* in) \ -{ \ - return longname(in, ll_convert_length(in)); \ -} \ -/* if passed string and length, extract its char pointer */ \ -inline auto longname( \ - const INSTR& in, \ - size_t len, \ - unsigned int code_page=ll_wstring_default_code_page()) \ -{ \ - return longname(in.c_str(), len, code_page); \ -} \ -/* if passed only a string object, no scan, pass known length */ \ -inline auto longname(const INSTR& in) \ -{ \ - return longname(in.c_str(), in.length()); \ -} \ -aliasmacro(OUTSTR, INSTR, longname(in)); \ -aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) - -ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string); -ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide); - ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring); - ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide); - -/** - * Converts incoming string into utf8 string - * - */ -LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); - -/// Get Windows message string for passed GetLastError() code -// VS 2013 doesn't let us forward-declare this template, which is what we -// started with, so the implementation could reference the specialization we -// haven't yet declared. Somewhat weirdly, just stating the generic -// implementation in terms of the specialization works, even in this order... - -// the general case is just a conversion from the sole implementation -// Microsoft says DWORD is a typedef for unsigned long -// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types -// so rather than drag windows.h into everybody's include space... -template -STRING windows_message(unsigned long error) -{ - return ll_convert(windows_message(error)); -} - -/// There's only one real implementation -template<> -LL_COMMON_API std::wstring windows_message(unsigned long error); - -/// Get Windows message string, implicitly calling GetLastError() -template -STRING windows_message() { return windows_message(GetLastError()); } - -//@} - -LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); - -#else // ! LL_WINDOWS - -LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); - -#endif // ! LL_WINDOWS - -/** - * Many of the 'strip' and 'replace' methods of LLStringUtilBase need - * specialization to work with the signed char type. - * Sadly, it is not possible (AFAIK) to specialize a single method of - * a template class. - * That stuff should go here. - */ -namespace LLStringFn -{ - /** - * @brief Replace all non-printable characters with replacement in - * string. - * NOTE - this will zap non-ascii - * - * @param [in,out] string the to modify. out value is the string - * with zero non-printable characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_nonprintable_in_ascii( - std::basic_string& string, - char replacement); - - - /** - * @brief Replace all non-printable characters and pipe characters - * with replacement in a string. - * NOTE - this will zap non-ascii - * - * @param [in,out] the string to modify. out value is the string - * with zero non-printable characters and zero pipe characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, - char replacement); - - - /** - * @brief Remove all characters that are not allowed in XML 1.0. - * Returns a copy of the string with those characters removed. - * Works with US ASCII and UTF-8 encoded strings. JC - */ - LL_COMMON_API std::string strip_invalid_xml(const std::string& input); - - - /** - * @brief Replace all control characters (0 <= c < 0x20) with replacement in - * string. This is safe for utf-8 - * - * @param [in,out] string the to modify. out value is the string - * with zero non-printable characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_ascii_controlchars( - std::basic_string& string, - char replacement); -} - -//////////////////////////////////////////////////////////// -// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. -// There is no LLWStringUtil::format implementation currently. -// Calling these for anything other than LLStringUtil will produce link errors. - -//////////////////////////////////////////////////////////// - -// static -template -std::vector::string_type> -LLStringUtilBase::getTokens(const string_type& instr, const string_type& delims) -{ - std::vector tokens; - getTokens(instr, tokens, delims); - return tokens; -} - -// static -template -std::vector::string_type> -LLStringUtilBase::getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes) -{ - std::vector tokens; - getTokens(instr, tokens, drop_delims, keep_delims, quotes); - return tokens; -} - -// static -template -std::vector::string_type> -LLStringUtilBase::getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes) -{ - std::vector tokens; - getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes); - return tokens; -} - -namespace LLStringUtilBaseImpl -{ - -/** - * Input string scanner helper for getTokens(), or really any other - * character-parsing routine that may have to deal with escape characters. - * This implementation defines the concept (also an interface, should you - * choose to implement the concept by subclassing) and provides trivial - * implementations for a string @em without escape processing. - */ -template -struct InString -{ - typedef std::basic_string string_type; - typedef typename string_type::const_iterator const_iterator; - - InString(const_iterator b, const_iterator e): - mIter(b), - mEnd(e) - {} - virtual ~InString() {} - - bool done() const { return mIter == mEnd; } - /// Is the current character (*mIter) escaped? This implementation can - /// answer trivially because it doesn't support escapes. - virtual bool escaped() const { return false; } - /// Obtain the current character and advance @c mIter. - virtual T next() { return *mIter++; } - /// Does the current character match specified character? - virtual bool is(T ch) const { return (! done()) && *mIter == ch; } - /// Is the current character any one of the specified characters? - virtual bool oneof(const string_type& delims) const - { - return (! done()) && LLStringUtilBase::contains(delims, *mIter); - } - - /** - * Scan forward from @from until either @a delim or end. This is primarily - * useful for processing quoted substrings. - * - * If we do see @a delim, append everything from @from until (excluding) - * @a delim to @a into, advance @c mIter to skip @a delim, and return @c - * true. - * - * If we do not see @a delim, do not alter @a into or @c mIter and return - * @c false. Do not pass GO, do not collect $200. - * - * @note The @c false case described above implements normal getTokens() - * treatment of an unmatched open quote: treat the quote character as if - * escaped, that is, simply collect it as part of the current token. Other - * plausible behaviors directly affect the way getTokens() deals with an - * unmatched quote: e.g. throwing an exception to treat it as an error, or - * assuming a close quote beyond end of string (in which case return @c - * true). - */ - virtual bool collect_until(string_type& into, const_iterator from, T delim) - { - const_iterator found = std::find(from, mEnd, delim); - // If we didn't find delim, change nothing, just tell caller. - if (found == mEnd) - return false; - // Found delim! Append everything between from and found. - into.append(from, found); - // advance past delim in input - mIter = found + 1; - return true; - } - - const_iterator mIter, mEnd; -}; - -/// InString subclass that handles escape characters -template -class InEscString: public InString -{ -public: - typedef InString super; - typedef typename super::string_type string_type; - typedef typename super::const_iterator const_iterator; - using super::done; - using super::mIter; - using super::mEnd; - - InEscString(const_iterator b, const_iterator e, const string_type& escapes): - super(b, e), - mEscapes(escapes) - { - // Even though we've already initialized 'mIter' via our base-class - // constructor, set it again to check for initial escape char. - setiter(b); - } - - /// This implementation uses the answer cached by setiter(). - virtual bool escaped() const { return mIsEsc; } - virtual T next() - { - // If we're looking at the escape character of an escape sequence, - // skip that character. This is the one time we can modify 'mIter' - // without using setiter: for this one case we DO NOT CARE if the - // escaped character is itself an escape. - if (mIsEsc) - ++mIter; - // If we were looking at an escape character, this is the escaped - // character; otherwise it's just the next character. - T result(*mIter); - // Advance mIter, checking for escape sequence. - setiter(mIter + 1); - return result; - } - - virtual bool is(T ch) const - { - // Like base-class is(), except that an escaped character matches - // nothing. - return (! done()) && (! mIsEsc) && *mIter == ch; - } - - virtual bool oneof(const string_type& delims) const - { - // Like base-class oneof(), except that an escaped character matches - // nothing. - return (! done()) && (! mIsEsc) && LLStringUtilBase::contains(delims, *mIter); - } - - virtual bool collect_until(string_type& into, const_iterator from, T delim) - { - // Deal with escapes in the characters we collect; that is, an escaped - // character must become just that character without the preceding - // escape. Collect characters in a separate string rather than - // directly appending to 'into' in case we do not find delim, in which - // case we're supposed to leave 'into' unmodified. - string_type collected; - // For scanning purposes, we're going to work directly with 'mIter'. - // Save its current value in case we fail to see delim. - const_iterator save_iter(mIter); - // Okay, set 'mIter', checking for escape. - setiter(from); - while (! done()) - { - // If we see an unescaped delim, stop and report success. - if ((! mIsEsc) && *mIter == delim) - { - // Append collected chars to 'into'. - into.append(collected); - // Don't forget to advance 'mIter' past delim. - setiter(mIter + 1); - return true; - } - // We're not at end, and either we're not looking at delim or it's - // escaped. Collect this character and keep going. - collected.push_back(next()); - } - // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell - // caller. - setiter(save_iter); - return false; - } - -private: - void setiter(const_iterator i) - { - mIter = i; - - // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively - // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches - // contains(mEscapes, *mIter). - - // We're looking at an escaped char if we're not already at end (that - // is, *mIter is even meaningful); if *mIter is in fact one of the - // specified escape characters; and if there's one more character - // following it. That is, if an escape character is the very last - // character of the input string, it loses its special meaning. - mIsEsc = (! done()) && - LLStringUtilBase::contains(mEscapes, *mIter) && - (mIter+1) != mEnd; - } - - const string_type mEscapes; - bool mIsEsc; -}; - -/// getTokens() implementation based on InString concept -template -void getTokens(INSTRING& instr, std::vector& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes) -{ - // There are times when we want to match either drop_delims or - // keep_delims. Concatenate them up front to speed things up. - string_type all_delims(drop_delims + keep_delims); - // no tokens yet - tokens.clear(); - - // try for another token - while (! instr.done()) - { - // scan past any drop_delims - while (instr.oneof(drop_delims)) - { - // skip this drop_delim - instr.next(); - // but if that was the end of the string, done - if (instr.done()) - return; - } - // found the start of another token: make a slot for it - tokens.push_back(string_type()); - if (instr.oneof(keep_delims)) - { - // *iter is a keep_delim, a token of exactly 1 character. Append - // that character to the new token and proceed. - tokens.back().push_back(instr.next()); - continue; - } - // Here we have a non-delimiter token, which might consist of a mix of - // quoted and unquoted parts. Use bash rules for quoting: you can - // embed a quoted substring in the midst of an unquoted token (e.g. - // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together - // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge - // from bash in that bash considers an unmatched quote an error. Our - // param signature doesn't allow for errors, so just pretend it's not - // a quote and embed it. - // At this level, keep scanning until we hit the next delimiter of - // either type (drop_delims or keep_delims). - while (! instr.oneof(all_delims)) - { - // If we're looking at an open quote, search forward for - // a close quote, collecting characters along the way. - if (instr.oneof(quotes) && - instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter)) - { - // collect_until is cleverly designed to do exactly what we - // need here. No further action needed if it returns true. - } - else - { - // Either *iter isn't a quote, or there's no matching close - // quote: in other words, just an ordinary char. Append it to - // current token. - tokens.back().push_back(instr.next()); - } - // having scanned that segment of this token, if we've reached the - // end of the string, we're done - if (instr.done()) - return; - } - } -} - -} // namespace LLStringUtilBaseImpl - -// static -template -void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes) -{ - // Because this overload doesn't support escapes, use simple InString to - // manage input range. - LLStringUtilBaseImpl::InString instring(string.begin(), string.end()); - LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); -} - -// static -template -void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes, const string_type& escapes) -{ - // This overload must deal with escapes. Delegate that to InEscString - // (unless there ARE no escapes). - std::unique_ptr< LLStringUtilBaseImpl::InString > instrp; - if (escapes.empty()) - instrp.reset(new LLStringUtilBaseImpl::InString(string.begin(), string.end())); - else - instrp.reset(new LLStringUtilBaseImpl::InEscString(string.begin(), string.end(), escapes)); - LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); -} - -// static -template -S32 LLStringUtilBase::compareStrings(const T* lhs, const T* rhs) -{ - S32 result; - if( lhs == rhs ) - { - result = 0; - } - else - if ( !lhs || !lhs[0] ) - { - result = ((!rhs || !rhs[0]) ? 0 : 1); - } - else - if ( !rhs || !rhs[0]) - { - result = -1; - } - else - { - result = LLStringOps::collate(lhs, rhs); - } - return result; -} - -//static -template -S32 LLStringUtilBase::compareStrings(const string_type& lhs, const string_type& rhs) -{ - return LLStringOps::collate(lhs.c_str(), rhs.c_str()); -} - -// static -template -S32 LLStringUtilBase::compareInsensitive(const T* lhs, const T* rhs ) -{ - S32 result; - if( lhs == rhs ) - { - result = 0; - } - else - if ( !lhs || !lhs[0] ) - { - result = ((!rhs || !rhs[0]) ? 0 : 1); - } - else - if ( !rhs || !rhs[0] ) - { - result = -1; - } - else - { - string_type lhs_string(lhs); - string_type rhs_string(rhs); - LLStringUtilBase::toUpper(lhs_string); - LLStringUtilBase::toUpper(rhs_string); - result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); - } - return result; -} - -//static -template -S32 LLStringUtilBase::compareInsensitive(const string_type& lhs, const string_type& rhs) -{ - string_type lhs_string(lhs); - string_type rhs_string(rhs); - LLStringUtilBase::toUpper(lhs_string); - LLStringUtilBase::toUpper(rhs_string); - return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); -} - -// Case sensitive comparison with good handling of numbers. Does not use current locale. -// a.k.a. strdictcmp() - -//static -template -S32 LLStringUtilBase::compareDict(const string_type& astr, const string_type& bstr) -{ - const T* a = astr.c_str(); - const T* b = bstr.c_str(); - T ca, cb; - S32 ai, bi, cnt = 0; - S32 bias = 0; - - ca = *(a++); - cb = *(b++); - while( ca && cb ){ - if( bias==0 ){ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; } - }else{ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } - } - if( LLStringOps::isDigit(ca) ){ - if( cnt-->0 ){ - if( cb!=ca ) break; - }else{ - if( !LLStringOps::isDigit(cb) ) break; - for(ai=0; LLStringOps::isDigit(a[ai]); ai++); - for(bi=0; LLStringOps::isDigit(b[bi]); bi++); - if( ai -S32 LLStringUtilBase::compareDictInsensitive(const string_type& astr, const string_type& bstr) -{ - const T* a = astr.c_str(); - const T* b = bstr.c_str(); - T ca, cb; - S32 ai, bi, cnt = 0; - - ca = *(a++); - cb = *(b++); - while( ca && cb ){ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } - if( LLStringOps::isDigit(ca) ){ - if( cnt-->0 ){ - if( cb!=ca ) break; - }else{ - if( !LLStringOps::isDigit(cb) ) break; - for(ai=0; LLStringOps::isDigit(a[ai]); ai++); - for(bi=0; LLStringOps::isDigit(b[bi]); bi++); - if( ai -bool LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) -{ - if( a.size() && b.size() ) - { - return (LLStringUtilBase::compareDict(a.c_str(), b.c_str()) < 0); - } - else - { - return (!b.empty()); - } -} - -//static -template -void LLStringUtilBase::toUpper(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toUpper); - } -} - -//static -template -void LLStringUtilBase::toLower(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toLower); - } -} - -//static -template -void LLStringUtilBase::trimHead(string_type& string) -{ - if( !string.empty() ) - { - size_type i = 0; - while( i < string.length() && LLStringOps::isSpace( string[i] ) ) - { - i++; - } - string.erase(0, i); - } -} - -//static -template -void LLStringUtilBase::trimTail(string_type& string) -{ - if( string.size() ) - { - size_type len = string.length(); - size_type i = len; - while( i > 0 && LLStringOps::isSpace( string[i-1] ) ) - { - i--; - } - - string.erase( i, len - i ); - } -} - - -// Replace line feeds with carriage return-line feed pairs. -//static -template -void LLStringUtilBase::addCRLF(string_type& string) -{ - const T LF = 10; - const T CR = 13; - - // Count the number of line feeds - size_type count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len; i++ ) - { - if( string[i] == LF ) - { - count++; - } - } - - // Insert a carriage return before each line feed - if( count ) - { - size_type size = len + count; - T *t = new T[size]; - size_type j = 0; - for( i = 0; i < len; ++i ) - { - if( string[i] == LF ) - { - t[j] = CR; - ++j; - } - t[j] = string[i]; - ++j; - } - - string.assign(t, size); - delete[] t; - } -} - -// Remove all carriage returns -//static -template -void LLStringUtilBase::removeCRLF(string_type& string) -{ - const T CR = 13; - - size_type cr_count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len - cr_count; i++ ) - { - if( string[i+cr_count] == CR ) - { - cr_count++; - } - - string[i] = string[i+cr_count]; - } - string.erase(i, cr_count); -} - -//static -template -void LLStringUtilBase::removeWindowsCR(string_type& string) -{ - if (string.empty()) - { - return; - } - const T LF = 10; - const T CR = 13; - - size_type cr_count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len - cr_count - 1; i++ ) - { - if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) - { - cr_count++; - } - - string[i] = string[i+cr_count]; - } - string.erase(i, cr_count); -} - -//static -template -void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) -{ - size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != string_type::npos ) - { - string[found_pos] = replacement; - found_pos++; // avoid infinite defeat if target == replacement - } -} - -//static -template -void LLStringUtilBase::replaceString( string_type& string, string_type target, string_type replacement ) -{ - size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != string_type::npos ) - { - string.replace( found_pos, target.length(), replacement ); - found_pos += replacement.length(); // avoid infinite defeat if replacement contains target - } -} - -//static -template -void LLStringUtilBase::replaceNonstandardASCII( string_type& string, T replacement ) -{ - const char LF = 10; - const S8 MIN = 32; -// const S8 MAX = 127; - - size_type len = string.size(); - for( size_type i = 0; i < len; i++ ) - { - // No need to test MAX < mText[i] because we treat mText[i] as a signed char, - // which has a max value of 127. - if( ( S8(string[i]) < MIN ) && (string[i] != LF) ) - { - string[i] = replacement; - } - } -} - -//static -template -void LLStringUtilBase::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab ) -{ - const T TAB = '\t'; - const T SPACE = ' '; - - string_type out_str; - // Replace tabs with spaces - for (size_type i = 0; i < str.length(); i++) - { - if (str[i] == TAB) - { - for (size_type j = 0; j < spaces_per_tab; j++) - out_str += SPACE; - } - else - { - out_str += str[i]; - } - } - str = out_str; -} - -//static -template -std::basic_string LLStringUtilBase::capitalize(const string_type& str) -{ - string_type result(str); - capitalize(result); - return result; -} - -//static -template -void LLStringUtilBase::capitalize(string_type& str) -{ - if (str.size()) - { - auto last = str[0] = toupper(str[0]); - for (U32 i = 1; i < str.size(); ++i) - { - last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i]; - } - } -} - -//static -template -bool LLStringUtilBase::containsNonprintable(const string_type& string) -{ - const char MIN = 32; - bool rv = false; - for (size_type i = 0; i < string.size(); i++) - { - if(string[i] < MIN) - { - rv = true; - break; - } - } - return rv; -} - -// *TODO: reimplement in terms of algorithm -//static -template -void LLStringUtilBase::stripNonprintable(string_type& string) -{ - const char MIN = 32; - size_type j = 0; - if (string.empty()) - { - return; - } - size_t src_size = string.size(); - char* c_string = new char[src_size + 1]; - if(c_string == NULL) - { - return; - } - copy(c_string, string.c_str(), src_size+1); - char* write_head = &c_string[0]; - for (size_type i = 0; i < src_size; i++) - { - char* read_head = &string[i]; - write_head = &c_string[j]; - if(!(*read_head < MIN)) - { - *write_head = *read_head; - ++j; - } - } - c_string[j]= '\0'; - string = c_string; - delete []c_string; -} - -// *TODO: reimplement in terms of algorithm -template -std::basic_string LLStringUtilBase::quote(const string_type& str, - const string_type& triggers, - const string_type& escape) -{ - size_type len(str.length()); - // If the string is already quoted, assume user knows what s/he's doing. - if (len >= 2 && str[0] == '"' && str[len-1] == '"') - { - return str; - } - - // Not already quoted: do we need to? triggers.empty() is a special case - // meaning "always quote." - if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos) - { - // no trigger characters, don't bother quoting - return str; - } - - // For whatever reason, we must quote this string. - string_type result; - result.push_back('"'); - for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) - { - if (*ci == '"') - { - result.append(escape); - } - result.push_back(*ci); - } - result.push_back('"'); - return result; -} - -template -void LLStringUtilBase::_makeASCII(string_type& string) -{ - // Replace non-ASCII chars with LL_UNKNOWN_CHAR - for (size_type i = 0; i < string.length(); i++) - { - if (string[i] > 0x7f) - { - string[i] = LL_UNKNOWN_CHAR; - } - } -} - -// static -template -void LLStringUtilBase::copy( T* dst, const T* src, size_type dst_size ) -{ - if( dst_size > 0 ) - { - size_type min_len = 0; - if( src ) - { - min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */ - memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */ - } - dst[min_len] = '\0'; - } -} - -// static -template -void LLStringUtilBase::copyInto(string_type& dst, const string_type& src, size_type offset) -{ - if ( offset == dst.length() ) - { - // special case - append to end of string and avoid expensive - // (when strings are large) string manipulations - dst += src; - } - else - { - string_type tail = dst.substr(offset); - - dst = dst.substr(0, offset); - dst += src; - dst += tail; - }; -} - -// True if this is the head of s. -//static -template -bool LLStringUtilBase::isHead( const string_type& string, const T* s ) -{ - if( string.empty() ) - { - // Early exit - return false; - } - else - { - return (strncmp( s, string.c_str(), string.size() ) == 0); - } -} - -// static -template -bool LLStringUtilBase::startsWith( - const string_type& string, - const string_type& substr) -{ - if(string.empty() || (substr.empty())) return false; - if (substr.length() > string.length()) return false; - if (0 == string.compare(0, substr.length(), substr)) return true; - return false; -} - -// static -template -bool LLStringUtilBase::endsWith( - const string_type& string, - const string_type& substr) -{ - if(string.empty() || (substr.empty())) return false; - size_t sub_len = substr.length(); - size_t str_len = string.length(); - if (sub_len > str_len) return false; - if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true; - return false; -} - -// static -template -auto LLStringUtilBase::getoptenv(const std::string& key) -> std::optional -{ - auto found(llstring_getoptenv(key)); - if (found) - { - // return populated std::optional - return { ll_convert(*found) }; - } - else - { - // empty std::optional - return {}; - } -} - -// static -template -auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt) -> string_type -{ - auto found(getoptenv(key)); - if (found) - { - return *found; - } - else - { - return dflt; - } -} - -template -bool LLStringUtilBase::convertToBOOL(const string_type& string, bool& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - if( - (temp == "1") || - (temp == "T") || - (temp == "t") || - (temp == "TRUE") || - (temp == "true") || - (temp == "True") ) - { - value = true; - return true; - } - else - if( - (temp == "0") || - (temp == "F") || - (temp == "f") || - (temp == "FALSE") || - (temp == "false") || - (temp == "False") ) - { - value = false; - return true; - } - - return false; -} - -template -bool LLStringUtilBase::convertToU8(const string_type& string, U8& value) -{ - S32 value32 = 0; - bool success = convertToS32(string, value32); - if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) - { - value = (U8) value32; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToS8(const string_type& string, S8& value) -{ - S32 value32 = 0; - bool success = convertToS32(string, value32); - if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) - { - value = (S8) value32; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToS16(const string_type& string, S16& value) -{ - S32 value32 = 0; - bool success = convertToS32(string, value32); - if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) - { - value = (S16) value32; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToU16(const string_type& string, U16& value) -{ - S32 value32 = 0; - bool success = convertToS32(string, value32); - if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) - { - value = (U16) value32; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToU32(const string_type& string, U32& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - U32 v; - std::basic_istringstream i_stream((string_type)temp); - if(i_stream >> v) - { - value = v; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToS32(const string_type& string, S32& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - S32 v; - std::basic_istringstream i_stream((string_type)temp); - if(i_stream >> v) - { - //TODO: figure out overflow and underflow reporting here - //if((LONG_MAX == v) || (LONG_MIN == v)) - //{ - // // Underflow or overflow - // return false; - //} - - value = v; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToF32(const string_type& string, F32& value) -{ - F64 value64 = 0.0; - bool success = convertToF64(string, value64); - if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) - { - value = (F32) value64; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToF64(const string_type& string, F64& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - F64 v; - std::basic_istringstream i_stream((string_type)temp); - if(i_stream >> v) - { - //TODO: figure out overflow and underflow reporting here - //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) - //{ - // // Underflow or overflow - // return false; - //} - - value = v; - return true; - } - return false; -} - -template -void LLStringUtilBase::truncate(string_type& string, size_type count) -{ - size_type cur_size = string.size(); - string.resize(count < cur_size ? count : cur_size); -} - -// The good thing about *declaration* macros, vs. usage macros, is that now -// we're done with them: we don't need them to bleed into the consuming source -// file. -#undef ll_convert_alias -#undef ll_convert_u16_alias -#undef ll_convert_wstr_alias -#undef LL_CONVERT_COPY_CHARS -#undef ll_convert_forms -#undef ll_convert_cp_forms - -#endif // LL_STRING_H +/** + * @file llstring.h + * @brief String utility functions and std::string class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTRING_H +#define LL_LLSTRING_H + +#include +#include +#include +#include +#include +#include // std::wcslen() +//#include +#include +#include +#include +#include +#include "llformat.h" + +#if LL_LINUX +#include +#include +#endif + +#include +#include + +const char LL_UNKNOWN_CHAR = '?'; +class LLSD; + +#if LL_DARWIN || LL_LINUX +// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already) +#include + +namespace std +{ +template<> +struct char_traits +{ + typedef U16 char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + typedef mbstate_t state_type; + + static void + assign(char_type& __c1, const char_type& __c2) + { __c1 = __c2; } + + static bool + eq(const char_type& __c1, const char_type& __c2) + { return __c1 == __c2; } + + static bool + lt(const char_type& __c1, const char_type& __c2) + { return __c1 < __c2; } + + static int + compare(const char_type* __s1, const char_type* __s2, size_t __n) + { return memcmp(__s1, __s2, __n * sizeof(char_type)); } + + static size_t + length(const char_type* __s) + { + const char_type *cur_char = __s; + while (*cur_char != 0) + { + ++cur_char; + } + return cur_char - __s; + } + + static const char_type* + find(const char_type* __s, size_t __n, const char_type& __a) + { return static_cast(memchr(__s, __a, __n * sizeof(char_type))); } + + static char_type* + move(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(memmove(__s1, __s2, __n * sizeof(char_type))); } + + static char_type* + copy(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */ + + static char_type* + assign(char_type* __s, size_t __n, char_type __a) + { + // This isn't right. + //return static_cast(memset(__s, __a, __n * sizeof(char_type))); + + // I don't think there's a standard 'memset' for 16-bit values. + // Do this the old-fashioned way. + + size_t __i; + for(__i = 0; __i < __n; __i++) + { + __s[__i] = __a; + } + return __s; + } + + static char_type + to_char_type(const int_type& __c) + { return static_cast(__c); } + + static int_type + to_int_type(const char_type& __c) + { return static_cast(__c); } + + static bool + eq_int_type(const int_type& __c1, const int_type& __c2) + { return __c1 == __c2; } + + static int_type + eof() { return static_cast(EOF); } + + static int_type + not_eof(const int_type& __c) + { return (__c == eof()) ? 0 : __c; } + }; +}; +#endif + +class LL_COMMON_API LLStringOps +{ +private: + static long sPacificTimeOffset; + static long sLocalTimeOffset; + static bool sPacificDaylightTime; + + static std::map datetimeToCodes; + +public: + static std::vector sWeekDayList; + static std::vector sWeekDayShortList; + static std::vector sMonthList; + static std::vector sMonthShortList; + static std::string sDayFormat; + + static std::string sAM; + static std::string sPM; + + static char toUpper(char elem) { return toupper((unsigned char)elem); } + static llwchar toUpper(llwchar elem) { return towupper(elem); } + + static char toLower(char elem) { return tolower((unsigned char)elem); } + static llwchar toLower(llwchar elem) { return towlower(elem); } + + static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; } + static bool isSpace(llwchar elem) { return iswspace(elem) != 0; } + + static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; } + static bool isUpper(llwchar elem) { return iswupper(elem) != 0; } + + static bool isLower(char elem) { return islower((unsigned char)elem) != 0; } + static bool isLower(llwchar elem) { return iswlower(elem) != 0; } + + static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; } + static bool isDigit(llwchar a) { return iswdigit(a) != 0; } + + static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; } + static bool isPunct(llwchar a) { return iswpunct(a) != 0; } + + static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; } + static bool isAlpha(llwchar a) { return iswalpha(a) != 0; } + + static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; } + static bool isAlnum(llwchar a) { return iswalnum(a) != 0; } + + // Returns true when 'a' corresponds to a "genuine" emoji. HB + static bool isEmoji(llwchar a); + + static S32 collate(const char* a, const char* b) { return strcoll(a, b); } + static S32 collate(const llwchar* a, const llwchar* b); + + static void setupDatetimeInfo(bool pacific_daylight_time); + + static void setupWeekDaysNames(const std::string& data); + static void setupWeekDaysShortNames(const std::string& data); + static void setupMonthNames(const std::string& data); + static void setupMonthShortNames(const std::string& data); + static void setupDayFormat(const std::string& data); + + + static long getPacificTimeOffset(void) { return sPacificTimeOffset;} + static long getLocalTimeOffset(void) { return sLocalTimeOffset;} + // Is the Pacific time zone (aka server time zone) + // currently in daylight savings time? + static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} + + static std::string getDatetimeCode (std::string key); + + // Express a value like 1234567 as "1.23M" + static std::string getReadableNumber(F64 num); +}; + +/** + * @brief Return a string constructed from in without crashing if the + * pointer is NULL. + */ +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); + + +// Allowing assignments from non-strings into format_map_t is apparently +// *really* error-prone, so subclass std::string with just basic c'tors. +class LLFormatMapString +{ +public: + LLFormatMapString() {}; + LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {}; + LLFormatMapString(const std::string& s) : mString(s) {}; + operator std::string() const { return mString; } + bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; } + std::size_t length() const { return mString.length(); } + +private: + std::string mString; +}; + +template +class LLStringUtilBase +{ +private: + static std::string sLocale; + +public: + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; + +public: + ///////////////////////////////////////////////////////////////////////////////////////// + // Static Utility functions that operate on std::strings + + static const string_type null; + + typedef std::map format_map_t; + /// considers any sequence of delims as a single field separator + LL_COMMON_API static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& delims); + /// like simple scan overload, but returns scanned vector + static std::vector getTokens(const string_type& instr, + const string_type& delims); + /// add support for keep_delims and quotes (either could be empty string) + static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// like keep_delims-and-quotes overload, but returns scanned vector + static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// add support for escapes (could be empty string) + static void getTokens(const string_type& instr, + std::vector& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + /// like escapes overload, but returns scanned vector + static std::vector getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + + LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); + LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); + LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); + LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions); + LL_COMMON_API static void setLocale (std::string inLocale); + LL_COMMON_API static std::string getLocale (void); + + static bool isValidIndex(const string_type& string, size_type i) + { + return !string.empty() && (0 <= i) && (i <= string.size()); + } + + static bool contains(const string_type& string, T c, size_type i=0) + { + return string.find(c, i) != string_type::npos; + } + + static void trimHead(string_type& string); + static void trimTail(string_type& string); + static void trim(string_type& string) { trimHead(string); trimTail(string); } + static void truncate(string_type& string, size_type count); + + static void toUpper(string_type& string); + static void toLower(string_type& string); + + // True if this is the head of s. + static bool isHead( const string_type& string, const T* s ); + + /** + * @brief Returns true if string starts with substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool startsWith( + const string_type& string, + const string_type& substr); + + /** + * @brief Returns true if string ends in substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool endsWith( + const string_type& string, + const string_type& substr); + + /** + * get environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by return value == dflt + */ + static string_type getenv(const std::string& key, const string_type& dflt=""); + /** + * get optional environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by (! return value) + */ + static std::optional getoptenv(const std::string& key); + + static void addCRLF(string_type& string); + static void removeCRLF(string_type& string); + static void removeWindowsCR(string_type& string); + + static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); + static void replaceNonstandardASCII( string_type& string, T replacement ); + static void replaceChar( string_type& string, T target, T replacement ); + static void replaceString( string_type& string, string_type target, string_type replacement ); + static string_type capitalize(const string_type& str); + static void capitalize(string_type& str); + + static bool containsNonprintable(const string_type& string); + static void stripNonprintable(string_type& string); + + /** + * Double-quote an argument string if needed, unless it's already + * double-quoted. Decide whether it's needed based on the presence of any + * character in @a triggers (default space or double-quote). If we quote + * it, escape any embedded double-quote with the @a escape string (default + * backslash). + * + * Passing triggers="" means always quote, unless it's already double-quoted. + */ + static string_type quote(const string_type& str, + const string_type& triggers=" \"", + const string_type& escape="\\"); + + /** + * @brief Unsafe way to make ascii characters. You should probably + * only call this when interacting with the host operating system. + * The 1 byte std::string does not work correctly. + * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII + * should work. + */ + static void _makeASCII(string_type& string); + + // Conversion to other data types + static bool convertToBOOL(const string_type& string, bool& value); + static bool convertToU8(const string_type& string, U8& value); + static bool convertToS8(const string_type& string, S8& value); + static bool convertToS16(const string_type& string, S16& value); + static bool convertToU16(const string_type& string, U16& value); + static bool convertToU32(const string_type& string, U32& value); + static bool convertToS32(const string_type& string, S32& value); + static bool convertToF32(const string_type& string, F32& value); + static bool convertToF64(const string_type& string, F64& value); + + ///////////////////////////////////////////////////////////////////////////////////////// + // Utility functions for working with char*'s and strings + + // Like strcmp but also handles empty strings. Uses + // current locale. + static S32 compareStrings(const T* lhs, const T* rhs); + static S32 compareStrings(const string_type& lhs, const string_type& rhs); + + // case insensitive version of above. Uses current locale on + // Win32, and falls back to a non-locale aware comparison on + // Linux. + static S32 compareInsensitive(const T* lhs, const T* rhs); + static S32 compareInsensitive(const string_type& lhs, const string_type& rhs); + + // Case sensitive comparison with good handling of numbers. Does not use current locale. + // a.k.a. strdictcmp() + static S32 compareDict(const string_type& a, const string_type& b); + + // Case *in*sensitive comparison with good handling of numbers. Does not use current locale. + // a.k.a. strdictcmp() + static S32 compareDictInsensitive(const string_type& a, const string_type& b); + + // Puts compareDict() in a form appropriate for LL container classes to use for sorting. + static bool precedesDict( const string_type& a, const string_type& b ); + + // A replacement for strncpy. + // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds + // up to dst_size-1 characters of src. + static void copy(T* dst, const T* src, size_type dst_size); + + // Copies src into dst at a given offset. + static void copyInto(string_type& dst, const string_type& src, size_type offset); + + static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } + + +#ifdef _DEBUG + LL_COMMON_API static void testHarness(); +#endif + +private: + LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector& tokens); +}; + +template const std::basic_string LLStringUtilBase::null; +template std::string LLStringUtilBase::sLocale; + +typedef LLStringUtilBase LLStringUtil; +typedef LLStringUtilBase LLWStringUtil; +typedef std::basic_string LLWString; + +//@ Use this where we want to disallow input in the form of "foo" +// This is used to catch places where english text is embedded in the code +// instead of in a translatable XUI file. +class LLStringExplicit : public std::string +{ +public: + explicit LLStringExplicit(const char* s) : std::string(s) {} + LLStringExplicit(const std::string& s) : std::string(s) {} + LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {} +}; + +struct LLDictionaryLess +{ +public: + bool operator()(const std::string& a, const std::string& b) const + { + return (LLStringUtil::precedesDict(a, b)); + } +}; + + +/** + * Simple support functions + */ + +/** + * @brief chop off the trailing characters in a string. + * + * This function works on bytes rather than glyphs, so this will + * incorrectly truncate non-single byte strings. + * Use utf8str_truncate() for utf8 strings + * @return a copy of in string minus the trailing count bytes. + */ +inline std::string chop_tail_copy( + const std::string& in, + std::string::size_type count) +{ + return std::string(in, 0, in.length() - count); +} + +/** + * @brief This translates a nybble stored as a hex value from 0-f back + * to a nybble in the low order bits of the return byte. + */ +LL_COMMON_API bool is_char_hex(char hex); +LL_COMMON_API U8 hex_as_nybble(char hex); + +/** + * @brief read the contents of a file into a string. + * + * Since this function has no concept of character encoding, most + * anything you do with this method ill-advised. Please avoid. + * @param str [out] The string which will have. + * @param filename The full name of the file to read. + * @return Returns true on success. If false, str is unmodified. + */ +LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem); + +/** + * Unicode support + */ + +/// generic conversion aliases +template +struct ll_convert_impl +{ + // Don't even provide a generic implementation. We specialize for every + // combination we do support. + TO operator()(const FROM& in) const; +}; + +// Use a function template to get the nice ll_convert(from_value) API. +template +TO ll_convert(const FROM& in) +{ + return ll_convert_impl()(in); +} + +// degenerate case +template +struct ll_convert_impl +{ + T operator()(const T& in) const { return in; } +}; + +// simple construction from char* +template +struct ll_convert_impl +{ + T operator()(const typename T::value_type* in) const { return { in }; } +}; + +// specialize ll_convert_impl to return EXPR +#define ll_convert_alias(TO, FROM, EXPR) \ +template<> \ +struct ll_convert_impl \ +{ \ + /* param_type optimally passes both char* and string */ \ + TO operator()(typename boost::call_traits::param_type in) const { return EXPR; } \ +} + +// If all we're doing is copying characters, pass this to ll_convert_alias as +// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl +// specialization above, it implies TO{ in.begin(), in.end() }. +#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() } + +// Generic name for strlen() / wcslen() - the default implementation should +// (!) work with U16 and llwchar, but we don't intend to engage it. +template +size_t ll_convert_length(const CHARTYPE* zstr) +{ + const CHARTYPE* zp; + // classic C string scan + for (zp = zstr; *zp; ++zp) + ; + return (zp - zstr); +} + +// specialize where we have a library function; may use intrinsic operations +template <> +inline size_t ll_convert_length(const wchar_t* zstr) { return std::wcslen(zstr); } +template <> +inline size_t ll_convert_length (const char* zstr) { return std::strlen(zstr); } + +// ll_convert_forms() is short for a bunch of boilerplate. It defines +// longname(const char*, len), longname(const char*), longname(const string&) +// and longname(const string&, len) so calls written pre-ll_convert() will +// work. Most of these overloads will be unified once we turn on C++17 and can +// use std::string_view. +// It also uses aliasmacro to ensure that both ll_convert(const char*) +// and ll_convert(const string&) will work. +#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ +LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ +inline auto longname(const INSTR& in, size_t len) \ +{ \ + return longname(in.c_str(), len); \ +} \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +/* string param */ \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +/* char* param */ \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + +// Make the incoming string a utf8 string. Replaces any unknown glyph +// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest +// of the data may not be recovered. +LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); + +// +// We should never use UTF16 except when communicating with Win32! +// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because +// the Windows APIs we want to call are all defined in terms of wchar_t* +// (or worse, LPCTSTR). +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types + +// While there is no point coding for an ASCII-only world (! defined(UNICODE)), +// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going +// forward, we should code in terms of wchar_t and std::wstring so as to +// support either setting of /Zc:wchar_t. + +// The first link above states that char can be used to hold ASCII or any +// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t +// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: +// * char and std::string always hold UTF-8 (of which ASCII is a subset). It +// is a BUG if they are used to pass strings in any other multi-byte +// encoding. +// * wchar_t and std::wstring should be our interface to Windows wide-string +// APIs, and therefore hold UTF-16LE. +// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do +// not introduce new uses of U16 or llutf16string for string data. +// * llwchar and LLWString hold UTF-32 strings. +// * Do not introduce char16_t or std::u16string. +// * Do not introduce char32_t or std::u32string. +// +// This typedef may or may not be identical to std::wstring, depending on +// LL_WCHAR_T_NATIVE. +typedef std::basic_string llutf16string; + +// Considering wchar_t, llwchar and U16, there are three relevant cases: +#if LLWCHAR_IS_WCHAR_T // every which way but Windows +// llwchar is identical to wchar_t, LLWString is identical to std::wstring. +// U16 is distinct, llutf16string is distinct (though pretty useless). +// Given conversions to/from LLWString and to/from llutf16string, conversions +// involving std::wstring would collide. +#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving llutf16string without collisions +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t +// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types. +// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string +// can all be defined. +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t- +// wchar_t is identical to U16, std::wstring is identical to llutf16string. +// Given conversions to/from LLWString and to/from std::wstring, conversions +// involving llutf16string would collide. +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving std::wstring without collisions +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#endif + +ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring); +ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str); +ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str); +ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring); + +// Same function, better name. JC +inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } + +LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); + +ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); +ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); + +// an older alias for utf16str_to_utf8str(llutf16string) +inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} + +// Length of this UTF32 string in bytes when transformed to UTF8 +LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); + +// Length in bytes of this wide char in a UTF8 string +LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); + +LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc); + +LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); + +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); + +// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr); + +/** + * @brief Properly truncate a utf8 string to a maximum byte count. + * + * The returned string may be less than max_len if the truncation + * happens in the middle of a glyph. If max_len is longer than the + * string passed in, the return value == utf8str. + * @param utf8str A valid utf8 string to truncate. + * @param max_len The maximum number of bytes in the return value. + * @return Returns a valid utf8 string with byte count <= max_len. + */ +LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); + +LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); + +LL_COMMON_API S32 utf8str_compare_insensitive( + const std::string& lhs, + const std::string& rhs); + +/** +* @brief Properly truncate a utf8 string to a maximum character count. +* +* If symbol_len is longer than the string passed in, the return +* value == utf8str. +* @param utf8str A valid utf8 string to truncate. +* @param symbol_len The maximum number of symbols in the return value. +* @return Returns a valid utf8 string with symbol count <= max_len. +*/ +LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len); + +/** + * @brief Replace all occurences of target_char with replace_char + * + * @param utf8str A utf8 string to process. + * @param target_char The wchar to be replaced + * @param replace_char The wchar which is written on replace + */ +LL_COMMON_API std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char); + +LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); + +// Hack - used for evil notecards. +LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); + +LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); + +LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); + +LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); + +LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr); + +LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr); + +LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str); + +#if LL_WINDOWS +/* @name Windows string helpers + */ +//@{ + +/** + * @brief Convert a wide string to/from std::string + * Convert a Windows wide string to/from our LLWString + * + * This replaces the unsafe W2A macro from ATL. + */ +// Avoid requiring this header to #include the Windows header file declaring +// our actual default code_page by delegating this function to our .cpp file. +LL_COMMON_API unsigned int ll_wstring_default_code_page(); + +// This is like ll_convert_forms(), with the added complexity of a code page +// parameter that may or may not be passed. +#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \ +/* declare the only nontrivial implementation (in .cpp file) */ \ +LL_COMMON_API OUTSTR longname( \ + const INSTR::value_type* in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()); \ +/* if passed only a char pointer, scan for nul terminator */ \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +/* if passed string and length, extract its char pointer */ \ +inline auto longname( \ + const INSTR& in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()) \ +{ \ + return longname(in.c_str(), len, code_page); \ +} \ +/* if passed only a string object, no scan, pass known length */ \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + +ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string); +ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide); + ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring); + ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide); + +/** + * Converts incoming string into utf8 string + * + */ +LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); + +/// Get Windows message string for passed GetLastError() code +// VS 2013 doesn't let us forward-declare this template, which is what we +// started with, so the implementation could reference the specialization we +// haven't yet declared. Somewhat weirdly, just stating the generic +// implementation in terms of the specialization works, even in this order... + +// the general case is just a conversion from the sole implementation +// Microsoft says DWORD is a typedef for unsigned long +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// so rather than drag windows.h into everybody's include space... +template +STRING windows_message(unsigned long error) +{ + return ll_convert(windows_message(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template +STRING windows_message() { return windows_message(GetLastError()); } + +//@} + +LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); + +#endif // ! LL_WINDOWS + +/** + * Many of the 'strip' and 'replace' methods of LLStringUtilBase need + * specialization to work with the signed char type. + * Sadly, it is not possible (AFAIK) to specialize a single method of + * a template class. + * That stuff should go here. + */ +namespace LLStringFn +{ + /** + * @brief Replace all non-printable characters with replacement in + * string. + * NOTE - this will zap non-ascii + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_nonprintable_in_ascii( + std::basic_string& string, + char replacement); + + + /** + * @brief Replace all non-printable characters and pipe characters + * with replacement in a string. + * NOTE - this will zap non-ascii + * + * @param [in,out] the string to modify. out value is the string + * with zero non-printable characters and zero pipe characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, + char replacement); + + + /** + * @brief Remove all characters that are not allowed in XML 1.0. + * Returns a copy of the string with those characters removed. + * Works with US ASCII and UTF-8 encoded strings. JC + */ + LL_COMMON_API std::string strip_invalid_xml(const std::string& input); + + + /** + * @brief Replace all control characters (0 <= c < 0x20) with replacement in + * string. This is safe for utf-8 + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_ascii_controlchars( + std::basic_string& string, + char replacement); +} + +//////////////////////////////////////////////////////////// +// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. +// There is no LLWStringUtil::format implementation currently. +// Calling these for anything other than LLStringUtil will produce link errors. + +//////////////////////////////////////////////////////////// + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, const string_type& delims) +{ + std::vector tokens; + getTokens(instr, tokens, delims); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes) +{ + std::vector tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes) +{ + std::vector tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes); + return tokens; +} + +namespace LLStringUtilBaseImpl +{ + +/** + * Input string scanner helper for getTokens(), or really any other + * character-parsing routine that may have to deal with escape characters. + * This implementation defines the concept (also an interface, should you + * choose to implement the concept by subclassing) and provides trivial + * implementations for a string @em without escape processing. + */ +template +struct InString +{ + typedef std::basic_string string_type; + typedef typename string_type::const_iterator const_iterator; + + InString(const_iterator b, const_iterator e): + mIter(b), + mEnd(e) + {} + virtual ~InString() {} + + bool done() const { return mIter == mEnd; } + /// Is the current character (*mIter) escaped? This implementation can + /// answer trivially because it doesn't support escapes. + virtual bool escaped() const { return false; } + /// Obtain the current character and advance @c mIter. + virtual T next() { return *mIter++; } + /// Does the current character match specified character? + virtual bool is(T ch) const { return (! done()) && *mIter == ch; } + /// Is the current character any one of the specified characters? + virtual bool oneof(const string_type& delims) const + { + return (! done()) && LLStringUtilBase::contains(delims, *mIter); + } + + /** + * Scan forward from @from until either @a delim or end. This is primarily + * useful for processing quoted substrings. + * + * If we do see @a delim, append everything from @from until (excluding) + * @a delim to @a into, advance @c mIter to skip @a delim, and return @c + * true. + * + * If we do not see @a delim, do not alter @a into or @c mIter and return + * @c false. Do not pass GO, do not collect $200. + * + * @note The @c false case described above implements normal getTokens() + * treatment of an unmatched open quote: treat the quote character as if + * escaped, that is, simply collect it as part of the current token. Other + * plausible behaviors directly affect the way getTokens() deals with an + * unmatched quote: e.g. throwing an exception to treat it as an error, or + * assuming a close quote beyond end of string (in which case return @c + * true). + */ + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + const_iterator found = std::find(from, mEnd, delim); + // If we didn't find delim, change nothing, just tell caller. + if (found == mEnd) + return false; + // Found delim! Append everything between from and found. + into.append(from, found); + // advance past delim in input + mIter = found + 1; + return true; + } + + const_iterator mIter, mEnd; +}; + +/// InString subclass that handles escape characters +template +class InEscString: public InString +{ +public: + typedef InString super; + typedef typename super::string_type string_type; + typedef typename super::const_iterator const_iterator; + using super::done; + using super::mIter; + using super::mEnd; + + InEscString(const_iterator b, const_iterator e, const string_type& escapes): + super(b, e), + mEscapes(escapes) + { + // Even though we've already initialized 'mIter' via our base-class + // constructor, set it again to check for initial escape char. + setiter(b); + } + + /// This implementation uses the answer cached by setiter(). + virtual bool escaped() const { return mIsEsc; } + virtual T next() + { + // If we're looking at the escape character of an escape sequence, + // skip that character. This is the one time we can modify 'mIter' + // without using setiter: for this one case we DO NOT CARE if the + // escaped character is itself an escape. + if (mIsEsc) + ++mIter; + // If we were looking at an escape character, this is the escaped + // character; otherwise it's just the next character. + T result(*mIter); + // Advance mIter, checking for escape sequence. + setiter(mIter + 1); + return result; + } + + virtual bool is(T ch) const + { + // Like base-class is(), except that an escaped character matches + // nothing. + return (! done()) && (! mIsEsc) && *mIter == ch; + } + + virtual bool oneof(const string_type& delims) const + { + // Like base-class oneof(), except that an escaped character matches + // nothing. + return (! done()) && (! mIsEsc) && LLStringUtilBase::contains(delims, *mIter); + } + + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + // Deal with escapes in the characters we collect; that is, an escaped + // character must become just that character without the preceding + // escape. Collect characters in a separate string rather than + // directly appending to 'into' in case we do not find delim, in which + // case we're supposed to leave 'into' unmodified. + string_type collected; + // For scanning purposes, we're going to work directly with 'mIter'. + // Save its current value in case we fail to see delim. + const_iterator save_iter(mIter); + // Okay, set 'mIter', checking for escape. + setiter(from); + while (! done()) + { + // If we see an unescaped delim, stop and report success. + if ((! mIsEsc) && *mIter == delim) + { + // Append collected chars to 'into'. + into.append(collected); + // Don't forget to advance 'mIter' past delim. + setiter(mIter + 1); + return true; + } + // We're not at end, and either we're not looking at delim or it's + // escaped. Collect this character and keep going. + collected.push_back(next()); + } + // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell + // caller. + setiter(save_iter); + return false; + } + +private: + void setiter(const_iterator i) + { + mIter = i; + + // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively + // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches + // contains(mEscapes, *mIter). + + // We're looking at an escaped char if we're not already at end (that + // is, *mIter is even meaningful); if *mIter is in fact one of the + // specified escape characters; and if there's one more character + // following it. That is, if an escape character is the very last + // character of the input string, it loses its special meaning. + mIsEsc = (! done()) && + LLStringUtilBase::contains(mEscapes, *mIter) && + (mIter+1) != mEnd; + } + + const string_type mEscapes; + bool mIsEsc; +}; + +/// getTokens() implementation based on InString concept +template +void getTokens(INSTRING& instr, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // There are times when we want to match either drop_delims or + // keep_delims. Concatenate them up front to speed things up. + string_type all_delims(drop_delims + keep_delims); + // no tokens yet + tokens.clear(); + + // try for another token + while (! instr.done()) + { + // scan past any drop_delims + while (instr.oneof(drop_delims)) + { + // skip this drop_delim + instr.next(); + // but if that was the end of the string, done + if (instr.done()) + return; + } + // found the start of another token: make a slot for it + tokens.push_back(string_type()); + if (instr.oneof(keep_delims)) + { + // *iter is a keep_delim, a token of exactly 1 character. Append + // that character to the new token and proceed. + tokens.back().push_back(instr.next()); + continue; + } + // Here we have a non-delimiter token, which might consist of a mix of + // quoted and unquoted parts. Use bash rules for quoting: you can + // embed a quoted substring in the midst of an unquoted token (e.g. + // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together + // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge + // from bash in that bash considers an unmatched quote an error. Our + // param signature doesn't allow for errors, so just pretend it's not + // a quote and embed it. + // At this level, keep scanning until we hit the next delimiter of + // either type (drop_delims or keep_delims). + while (! instr.oneof(all_delims)) + { + // If we're looking at an open quote, search forward for + // a close quote, collecting characters along the way. + if (instr.oneof(quotes) && + instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter)) + { + // collect_until is cleverly designed to do exactly what we + // need here. No further action needed if it returns true. + } + else + { + // Either *iter isn't a quote, or there's no matching close + // quote: in other words, just an ordinary char. Append it to + // current token. + tokens.back().push_back(instr.next()); + } + // having scanned that segment of this token, if we've reached the + // end of the string, we're done + if (instr.done()) + return; + } + } +} + +} // namespace LLStringUtilBaseImpl + +// static +template +void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // Because this overload doesn't support escapes, use simple InString to + // manage input range. + LLStringUtilBaseImpl::InString instring(string.begin(), string.end()); + LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); +} + +// static +template +void LLStringUtilBase::getTokens(const string_type& string, std::vector& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes, const string_type& escapes) +{ + // This overload must deal with escapes. Delegate that to InEscString + // (unless there ARE no escapes). + std::unique_ptr< LLStringUtilBaseImpl::InString > instrp; + if (escapes.empty()) + instrp.reset(new LLStringUtilBaseImpl::InString(string.begin(), string.end())); + else + instrp.reset(new LLStringUtilBaseImpl::InEscString(string.begin(), string.end(), escapes)); + LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); +} + +// static +template +S32 LLStringUtilBase::compareStrings(const T* lhs, const T* rhs) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0]) + { + result = -1; + } + else + { + result = LLStringOps::collate(lhs, rhs); + } + return result; +} + +//static +template +S32 LLStringUtilBase::compareStrings(const string_type& lhs, const string_type& rhs) +{ + return LLStringOps::collate(lhs.c_str(), rhs.c_str()); +} + +// static +template +S32 LLStringUtilBase::compareInsensitive(const T* lhs, const T* rhs ) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0] ) + { + result = -1; + } + else + { + string_type lhs_string(lhs); + string_type rhs_string(rhs); + LLStringUtilBase::toUpper(lhs_string); + LLStringUtilBase::toUpper(rhs_string); + result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); + } + return result; +} + +//static +template +S32 LLStringUtilBase::compareInsensitive(const string_type& lhs, const string_type& rhs) +{ + string_type lhs_string(lhs); + string_type rhs_string(rhs); + LLStringUtilBase::toUpper(lhs_string); + LLStringUtilBase::toUpper(rhs_string); + return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); +} + +// Case sensitive comparison with good handling of numbers. Does not use current locale. +// a.k.a. strdictcmp() + +//static +template +S32 LLStringUtilBase::compareDict(const string_type& astr, const string_type& bstr) +{ + const T* a = astr.c_str(); + const T* b = bstr.c_str(); + T ca, cb; + S32 ai, bi, cnt = 0; + S32 bias = 0; + + ca = *(a++); + cb = *(b++); + while( ca && cb ){ + if( bias==0 ){ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; } + }else{ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } + } + if( LLStringOps::isDigit(ca) ){ + if( cnt-->0 ){ + if( cb!=ca ) break; + }else{ + if( !LLStringOps::isDigit(cb) ) break; + for(ai=0; LLStringOps::isDigit(a[ai]); ai++); + for(bi=0; LLStringOps::isDigit(b[bi]); bi++); + if( ai +S32 LLStringUtilBase::compareDictInsensitive(const string_type& astr, const string_type& bstr) +{ + const T* a = astr.c_str(); + const T* b = bstr.c_str(); + T ca, cb; + S32 ai, bi, cnt = 0; + + ca = *(a++); + cb = *(b++); + while( ca && cb ){ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } + if( LLStringOps::isDigit(ca) ){ + if( cnt-->0 ){ + if( cb!=ca ) break; + }else{ + if( !LLStringOps::isDigit(cb) ) break; + for(ai=0; LLStringOps::isDigit(a[ai]); ai++); + for(bi=0; LLStringOps::isDigit(b[bi]); bi++); + if( ai +bool LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) +{ + if( a.size() && b.size() ) + { + return (LLStringUtilBase::compareDict(a.c_str(), b.c_str()) < 0); + } + else + { + return (!b.empty()); + } +} + +//static +template +void LLStringUtilBase::toUpper(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toUpper); + } +} + +//static +template +void LLStringUtilBase::toLower(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toLower); + } +} + +//static +template +void LLStringUtilBase::trimHead(string_type& string) +{ + if( !string.empty() ) + { + size_type i = 0; + while( i < string.length() && LLStringOps::isSpace( string[i] ) ) + { + i++; + } + string.erase(0, i); + } +} + +//static +template +void LLStringUtilBase::trimTail(string_type& string) +{ + if( string.size() ) + { + size_type len = string.length(); + size_type i = len; + while( i > 0 && LLStringOps::isSpace( string[i-1] ) ) + { + i--; + } + + string.erase( i, len - i ); + } +} + + +// Replace line feeds with carriage return-line feed pairs. +//static +template +void LLStringUtilBase::addCRLF(string_type& string) +{ + const T LF = 10; + const T CR = 13; + + // Count the number of line feeds + size_type count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len; i++ ) + { + if( string[i] == LF ) + { + count++; + } + } + + // Insert a carriage return before each line feed + if( count ) + { + size_type size = len + count; + T *t = new T[size]; + size_type j = 0; + for( i = 0; i < len; ++i ) + { + if( string[i] == LF ) + { + t[j] = CR; + ++j; + } + t[j] = string[i]; + ++j; + } + + string.assign(t, size); + delete[] t; + } +} + +// Remove all carriage returns +//static +template +void LLStringUtilBase::removeCRLF(string_type& string) +{ + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count; i++ ) + { + if( string[i+cr_count] == CR ) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template +void LLStringUtilBase::removeWindowsCR(string_type& string) +{ + if (string.empty()) + { + return; + } + const T LF = 10; + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count - 1; i++ ) + { + if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template +void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) +{ + size_type found_pos = 0; + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) + { + string[found_pos] = replacement; + found_pos++; // avoid infinite defeat if target == replacement + } +} + +//static +template +void LLStringUtilBase::replaceString( string_type& string, string_type target, string_type replacement ) +{ + size_type found_pos = 0; + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) + { + string.replace( found_pos, target.length(), replacement ); + found_pos += replacement.length(); // avoid infinite defeat if replacement contains target + } +} + +//static +template +void LLStringUtilBase::replaceNonstandardASCII( string_type& string, T replacement ) +{ + const char LF = 10; + const S8 MIN = 32; +// const S8 MAX = 127; + + size_type len = string.size(); + for( size_type i = 0; i < len; i++ ) + { + // No need to test MAX < mText[i] because we treat mText[i] as a signed char, + // which has a max value of 127. + if( ( S8(string[i]) < MIN ) && (string[i] != LF) ) + { + string[i] = replacement; + } + } +} + +//static +template +void LLStringUtilBase::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab ) +{ + const T TAB = '\t'; + const T SPACE = ' '; + + string_type out_str; + // Replace tabs with spaces + for (size_type i = 0; i < str.length(); i++) + { + if (str[i] == TAB) + { + for (size_type j = 0; j < spaces_per_tab; j++) + out_str += SPACE; + } + else + { + out_str += str[i]; + } + } + str = out_str; +} + +//static +template +std::basic_string LLStringUtilBase::capitalize(const string_type& str) +{ + string_type result(str); + capitalize(result); + return result; +} + +//static +template +void LLStringUtilBase::capitalize(string_type& str) +{ + if (str.size()) + { + auto last = str[0] = toupper(str[0]); + for (U32 i = 1; i < str.size(); ++i) + { + last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i]; + } + } +} + +//static +template +bool LLStringUtilBase::containsNonprintable(const string_type& string) +{ + const char MIN = 32; + bool rv = false; + for (size_type i = 0; i < string.size(); i++) + { + if(string[i] < MIN) + { + rv = true; + break; + } + } + return rv; +} + +// *TODO: reimplement in terms of algorithm +//static +template +void LLStringUtilBase::stripNonprintable(string_type& string) +{ + const char MIN = 32; + size_type j = 0; + if (string.empty()) + { + return; + } + size_t src_size = string.size(); + char* c_string = new char[src_size + 1]; + if(c_string == NULL) + { + return; + } + copy(c_string, string.c_str(), src_size+1); + char* write_head = &c_string[0]; + for (size_type i = 0; i < src_size; i++) + { + char* read_head = &string[i]; + write_head = &c_string[j]; + if(!(*read_head < MIN)) + { + *write_head = *read_head; + ++j; + } + } + c_string[j]= '\0'; + string = c_string; + delete []c_string; +} + +// *TODO: reimplement in terms of algorithm +template +std::basic_string LLStringUtilBase::quote(const string_type& str, + const string_type& triggers, + const string_type& escape) +{ + size_type len(str.length()); + // If the string is already quoted, assume user knows what s/he's doing. + if (len >= 2 && str[0] == '"' && str[len-1] == '"') + { + return str; + } + + // Not already quoted: do we need to? triggers.empty() is a special case + // meaning "always quote." + if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos) + { + // no trigger characters, don't bother quoting + return str; + } + + // For whatever reason, we must quote this string. + string_type result; + result.push_back('"'); + for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) + { + if (*ci == '"') + { + result.append(escape); + } + result.push_back(*ci); + } + result.push_back('"'); + return result; +} + +template +void LLStringUtilBase::_makeASCII(string_type& string) +{ + // Replace non-ASCII chars with LL_UNKNOWN_CHAR + for (size_type i = 0; i < string.length(); i++) + { + if (string[i] > 0x7f) + { + string[i] = LL_UNKNOWN_CHAR; + } + } +} + +// static +template +void LLStringUtilBase::copy( T* dst, const T* src, size_type dst_size ) +{ + if( dst_size > 0 ) + { + size_type min_len = 0; + if( src ) + { + min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */ + memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */ + } + dst[min_len] = '\0'; + } +} + +// static +template +void LLStringUtilBase::copyInto(string_type& dst, const string_type& src, size_type offset) +{ + if ( offset == dst.length() ) + { + // special case - append to end of string and avoid expensive + // (when strings are large) string manipulations + dst += src; + } + else + { + string_type tail = dst.substr(offset); + + dst = dst.substr(0, offset); + dst += src; + dst += tail; + }; +} + +// True if this is the head of s. +//static +template +bool LLStringUtilBase::isHead( const string_type& string, const T* s ) +{ + if( string.empty() ) + { + // Early exit + return false; + } + else + { + return (strncmp( s, string.c_str(), string.size() ) == 0); + } +} + +// static +template +bool LLStringUtilBase::startsWith( + const string_type& string, + const string_type& substr) +{ + if(string.empty() || (substr.empty())) return false; + if (substr.length() > string.length()) return false; + if (0 == string.compare(0, substr.length(), substr)) return true; + return false; +} + +// static +template +bool LLStringUtilBase::endsWith( + const string_type& string, + const string_type& substr) +{ + if(string.empty() || (substr.empty())) return false; + size_t sub_len = substr.length(); + size_t str_len = string.length(); + if (sub_len > str_len) return false; + if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true; + return false; +} + +// static +template +auto LLStringUtilBase::getoptenv(const std::string& key) -> std::optional +{ + auto found(llstring_getoptenv(key)); + if (found) + { + // return populated std::optional + return { ll_convert(*found) }; + } + else + { + // empty std::optional + return {}; + } +} + +// static +template +auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt) -> string_type +{ + auto found(getoptenv(key)); + if (found) + { + return *found; + } + else + { + return dflt; + } +} + +template +bool LLStringUtilBase::convertToBOOL(const string_type& string, bool& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + if( + (temp == "1") || + (temp == "T") || + (temp == "t") || + (temp == "TRUE") || + (temp == "true") || + (temp == "True") ) + { + value = true; + return true; + } + else + if( + (temp == "0") || + (temp == "F") || + (temp == "f") || + (temp == "FALSE") || + (temp == "false") || + (temp == "False") ) + { + value = false; + return true; + } + + return false; +} + +template +bool LLStringUtilBase::convertToU8(const string_type& string, U8& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) + { + value = (U8) value32; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToS8(const string_type& string, S8& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) + { + value = (S8) value32; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToS16(const string_type& string, S16& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) + { + value = (S16) value32; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToU16(const string_type& string, U16& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) + { + value = (U16) value32; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToU32(const string_type& string, U32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + U32 v; + std::basic_istringstream i_stream((string_type)temp); + if(i_stream >> v) + { + value = v; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToS32(const string_type& string, S32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + S32 v; + std::basic_istringstream i_stream((string_type)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if((LONG_MAX == v) || (LONG_MIN == v)) + //{ + // // Underflow or overflow + // return false; + //} + + value = v; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToF32(const string_type& string, F32& value) +{ + F64 value64 = 0.0; + bool success = convertToF64(string, value64); + if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) + { + value = (F32) value64; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToF64(const string_type& string, F64& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + F64 v; + std::basic_istringstream i_stream((string_type)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) + //{ + // // Underflow or overflow + // return false; + //} + + value = v; + return true; + } + return false; +} + +template +void LLStringUtilBase::truncate(string_type& string, size_type count) +{ + size_type cur_size = string.size(); + string.resize(count < cur_size ? count : cur_size); +} + +// The good thing about *declaration* macros, vs. usage macros, is that now +// we're done with them: we don't need them to bleed into the consuming source +// file. +#undef ll_convert_alias +#undef ll_convert_u16_alias +#undef ll_convert_wstr_alias +#undef LL_CONVERT_COPY_CHARS +#undef ll_convert_forms +#undef ll_convert_cp_forms + +#endif // LL_STRING_H diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index a9b38fb14b..97f95369e5 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -1,209 +1,209 @@ -/** - * @file llstringtable.h - * @brief The LLStringTable class provides a _fast_ method for finding - * unique copies of strings. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_STRING_TABLE_H -#define LL_STRING_TABLE_H - -#include "lldefs.h" -#include "llformat.h" -#include "llstl.h" -#include -#include - -#if LL_WINDOWS -# if (_MSC_VER >= 1300 && _MSC_VER < 1400) -# define STRING_TABLE_HASH_MAP 1 -# endif -#else -//# define STRING_TABLE_HASH_MAP 1 -#endif - -const U32 MAX_STRINGS_LENGTH = 256; - -class LL_COMMON_API LLStringTableEntry -{ -public: - LLStringTableEntry(const char *str); - ~LLStringTableEntry(); - - void incCount() { mCount++; } - bool decCount() { return --mCount != 0; } - - char *mString; - S32 mCount; -}; - -class LL_COMMON_API LLStringTable -{ -public: - LLStringTable(int tablesize); - ~LLStringTable(); - - char *checkString(const char *str); - char *checkString(const std::string& str); - LLStringTableEntry *checkStringEntry(const char *str); - LLStringTableEntry *checkStringEntry(const std::string& str); - - char *addString(const char *str); - char *addString(const std::string& str); - LLStringTableEntry *addStringEntry(const char *str); - LLStringTableEntry *addStringEntry(const std::string& str); - void removeString(const char *str); - - S32 mMaxEntries; - S32 mUniqueEntries; - -#if STRING_TABLE_HASH_MAP -#if LL_WINDOWS - typedef std::hash_multimap string_hash_t; -#else - typedef __gnu_cxx::hash_multimap string_hash_t; -#endif - string_hash_t mStringHash; -#else - typedef std::list string_list_t; - typedef string_list_t * string_list_ptr_t; - string_list_ptr_t *mStringList; -#endif -}; - -extern LL_COMMON_API LLStringTable gStringTable; - -//============================================================================ - -// This class is designed to be used locally, -// e.g. as a member of an LLXmlTree -// Strings can be inserted only, then quickly looked up - -typedef const std::string* LLStdStringHandle; - -class LL_COMMON_API LLStdStringTable -{ -public: - LLStdStringTable(S32 tablesize = 0) - { - if (tablesize == 0) - { - tablesize = 256; // default - } - // Make sure tablesize is power of 2 - for (S32 i = 31; i>0; i--) - { - if (tablesize & (1<= (3<<(i-1))) - tablesize = (1<<(i+1)); - else - tablesize = (1< > string_set_t; - string_set_t* mStringList; // [mTableSize] -}; - - -#endif +/** + * @file llstringtable.h + * @brief The LLStringTable class provides a _fast_ method for finding + * unique copies of strings. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_STRING_TABLE_H +#define LL_STRING_TABLE_H + +#include "lldefs.h" +#include "llformat.h" +#include "llstl.h" +#include +#include + +#if LL_WINDOWS +# if (_MSC_VER >= 1300 && _MSC_VER < 1400) +# define STRING_TABLE_HASH_MAP 1 +# endif +#else +//# define STRING_TABLE_HASH_MAP 1 +#endif + +const U32 MAX_STRINGS_LENGTH = 256; + +class LL_COMMON_API LLStringTableEntry +{ +public: + LLStringTableEntry(const char *str); + ~LLStringTableEntry(); + + void incCount() { mCount++; } + bool decCount() { return --mCount != 0; } + + char *mString; + S32 mCount; +}; + +class LL_COMMON_API LLStringTable +{ +public: + LLStringTable(int tablesize); + ~LLStringTable(); + + char *checkString(const char *str); + char *checkString(const std::string& str); + LLStringTableEntry *checkStringEntry(const char *str); + LLStringTableEntry *checkStringEntry(const std::string& str); + + char *addString(const char *str); + char *addString(const std::string& str); + LLStringTableEntry *addStringEntry(const char *str); + LLStringTableEntry *addStringEntry(const std::string& str); + void removeString(const char *str); + + S32 mMaxEntries; + S32 mUniqueEntries; + +#if STRING_TABLE_HASH_MAP +#if LL_WINDOWS + typedef std::hash_multimap string_hash_t; +#else + typedef __gnu_cxx::hash_multimap string_hash_t; +#endif + string_hash_t mStringHash; +#else + typedef std::list string_list_t; + typedef string_list_t * string_list_ptr_t; + string_list_ptr_t *mStringList; +#endif +}; + +extern LL_COMMON_API LLStringTable gStringTable; + +//============================================================================ + +// This class is designed to be used locally, +// e.g. as a member of an LLXmlTree +// Strings can be inserted only, then quickly looked up + +typedef const std::string* LLStdStringHandle; + +class LL_COMMON_API LLStdStringTable +{ +public: + LLStdStringTable(S32 tablesize = 0) + { + if (tablesize == 0) + { + tablesize = 256; // default + } + // Make sure tablesize is power of 2 + for (S32 i = 31; i>0; i--) + { + if (tablesize & (1<= (3<<(i-1))) + tablesize = (1<<(i+1)); + else + tablesize = (1< > string_set_t; + string_set_t* mStringList; // [mTableSize] +}; + + +#endif diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 02fd0733e5..496ec0d869 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1,1414 +1,1414 @@ -/** - * @file llsys.cpp - * @brief Implementation of the basic system query functions. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#if LL_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - -#include "linden_common.h" - -#include "llsys.h" - -#include -#ifdef LL_USESYSTEMLIBS -# include -#else -# include "zlib-ng/zlib.h" -#endif - -#include "llprocessor.h" -#include "llerrorcontrol.h" -#include "llevents.h" -#include "llformat.h" -#include "llregex.h" -#include "lltimer.h" -#include "llsdserialize.h" -#include "llsdutil.h" -#include -#include -#include -#include -#include -#include -#include -#include "llfasttimer.h" - -using namespace llsd; - -#if LL_WINDOWS -# include "llwin32headerslean.h" -# include // GetPerformanceInfo() et al. -# include -#elif LL_DARWIN -# include "llsys_objc.h" -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -#elif LL_LINUX -# include -# include -# include -# include -# include -const char MEMINFO_FILE[] = "/proc/meminfo"; -# include -#endif - -LLCPUInfo gSysCPU; - -// Don't log memory info any more often than this. It also serves as our -// framerate sample size. -static const F32 MEM_INFO_THROTTLE = 20; -// Sliding window of samples. We intentionally limit the length of time we -// remember "the slowest" framerate because framerate is very slow at login. -// If we only triggered FrameWatcher logging when the session framerate -// dropped below the login framerate, we'd have very little additional data. -static const F32 MEM_INFO_WINDOW = 10*60; - -LLOSInfo::LLOSInfo() : - mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("") -{ - -#if LL_WINDOWS - - if (IsWindows10OrGreater()) - { - mMajorVer = 10; - mMinorVer = 0; - mOSStringSimple = "Microsoft Windows 10 "; - } - else if (IsWindows8Point1OrGreater()) - { - mMajorVer = 6; - mMinorVer = 3; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2012 R2 "; - } - else - { - mOSStringSimple = "Microsoft Windows 8.1 "; - } - } - else if (IsWindows8OrGreater()) - { - mMajorVer = 6; - mMinorVer = 2; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2012 "; - } - else - { - mOSStringSimple = "Microsoft Windows 8 "; - } - } - else if (IsWindows7SP1OrGreater()) - { - mMajorVer = 6; - mMinorVer = 1; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 R2 SP1 "; - } - else - { - mOSStringSimple = "Microsoft Windows 7 SP1 "; - } - } - else if (IsWindows7OrGreater()) - { - mMajorVer = 6; - mMinorVer = 1; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 R2 "; - } - else - { - mOSStringSimple = "Microsoft Windows 7 "; - } - } - else if (IsWindowsVistaSP2OrGreater()) - { - mMajorVer = 6; - mMinorVer = 0; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 SP2 "; - } - else - { - mOSStringSimple = "Microsoft Windows Vista SP2 "; - } - } - else - { - mOSStringSimple = "Unsupported Windows version "; - } - - ///get native system info if available.. - typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo - SYSTEM_INFO si; //System Info object file contains architecture info - PGNSI pGNSI; //pointer object - ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information - pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function - if (NULL != pGNSI) //check if it has failed - pGNSI(&si); //success - else - GetSystemInfo(&si); //if it fails get regular system info - //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) - - // Try calling GetVersionEx using the OSVERSIONINFOEX structure. - OSVERSIONINFOEX osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (GetVersionEx((OSVERSIONINFO *)&osvi)) - { - mBuild = osvi.dwBuildNumber & 0xffff; - } - else - { - // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (GetVersionEx((OSVERSIONINFO *)&osvi)) - { - mBuild = osvi.dwBuildNumber & 0xffff; - } - } - - S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry - if (mMajorVer == 10) - { - DWORD cbData(sizeof(DWORD)); - DWORD data(0); - HKEY key; - LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); - if (ERROR_SUCCESS == ret_code) - { - ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast(&data), &cbData); - if (ERROR_SUCCESS == ret_code) - { - ubr = data; - } - } - - if (mBuild >= 22000) - { - // At release Windows 11 version was 10.0.22000.194 - // Windows 10 version was 10.0.19043.1266 - // There is no warranty that Win10 build won't increase, - // so until better solution is found or Microsoft updates - // SDK with IsWindows11OrGreater(), indicate "10/11" - // - // Current alternatives: - // Query WMI's Win32_OperatingSystem for OS string. Slow - // and likely to return 'compatibility' string. - // Check presence of dlls/libs or may be their version. - mOSStringSimple = "Microsoft Windows 10/11 "; - } - } - - //msdn microsoft finds 32 bit and 64 bit flavors this way.. - //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors - //of windows than this code does (in case it is needed for the future) - if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit - { - mOSStringSimple += "64-bit "; - } - else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) - { - mOSStringSimple += "32-bit "; - } - - mOSString = mOSStringSimple; - if (mBuild > 0) - { - mOSString += llformat("(Build %d", mBuild); - if (ubr > 0) - { - mOSString += llformat(".%d", ubr); - } - mOSString += ")"; - } - - LLStringUtil::trim(mOSStringSimple); - LLStringUtil::trim(mOSString); - -#elif LL_DARWIN - - // Initialize mOSStringSimple to something like: - // "macOS 10.13.1" - { - const char * DARWIN_PRODUCT_NAME = "macOS"; - - int64_t major_version, minor_version, bugfix_version = 0; - - if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version)) - { - mMajorVer = major_version; - mMinorVer = minor_version; - mBuild = bugfix_version; - - std::stringstream os_version_string; - os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild; - - // Put it in the OS string we are compiling - mOSStringSimple.append(os_version_string.str()); - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - } - } - - // Initialize mOSString to something like: - // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" - struct utsname un; - if(uname(&un) != -1) - { - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.sysname); - mOSString.append(" "); - mOSString.append(un.release); - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - } - else - { - mOSString = mOSStringSimple; - } - -#elif LL_LINUX - - struct utsname un; - if(uname(&un) != -1) - { - mOSStringSimple.append(un.sysname); - mOSStringSimple.append(" "); - mOSStringSimple.append(un.release); - - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - - // Simplify 'Simple' - std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); - if (ostype == "Linux") - { - // Only care about major and minor Linux versions, truncate at second '.' - std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); - std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; - std::string simple = mOSStringSimple.substr(0, idx2); - if (simple.length() > 0) - mOSStringSimple = simple; - } - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - mOSString = mOSStringSimple; - } - - const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"; - boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION); - boost::smatch matched; - - std::string glibc_version(gnu_get_libc_version()); - if ( ll_regex_match(glibc_version, matched, os_version_parse) ) - { - LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL; - - std::string version_value; - - if ( matched[1].matched ) // Major version - { - version_value.assign(matched[1].first, matched[1].second); - if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1) - { - LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_ERRS("AppInit") - << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION - << "' returned true, but major version [1] did not match" - << LL_ENDL; - } - - if ( matched[2].matched ) // Minor version - { - version_value.assign(matched[2].first, matched[2].second); - if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1) - { - LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_ERRS("AppInit") - << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION - << "' returned true, but minor version [1] did not match" - << LL_ENDL; - } - - if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.' - { - version_value.assign(matched[4].first, matched[4].second); - if (sscanf(version_value.c_str(), "%d", &mBuild) != 1) - { - LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_INFOS("AppInit") - << "OS build version not provided; using zero" - << LL_ENDL; - } - } - else - { - LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL; - } - -#else - - struct utsname un; - if(uname(&un) != -1) - { - mOSStringSimple.append(un.sysname); - mOSStringSimple.append(" "); - mOSStringSimple.append(un.release); - - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - - // Simplify 'Simple' - std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); - if (ostype == "Linux") - { - // Only care about major and minor Linux versions, truncate at second '.' - std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); - std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; - std::string simple = mOSStringSimple.substr(0, idx2); - if (simple.length() > 0) - mOSStringSimple = simple; - } - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - mOSString = mOSStringSimple; - } - -#endif - - std::stringstream dotted_version_string; - dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild; - mOSVersionString.append(dotted_version_string.str()); - - mOSBitness = is64Bit() ? 64 : 32; - LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL; -} - -#ifndef LL_WINDOWS -// static -long LLOSInfo::getMaxOpenFiles() -{ - const long OPEN_MAX_GUESS = 256; - -#ifdef OPEN_MAX - static long open_max = OPEN_MAX; -#else - static long open_max = 0; -#endif - - if (0 == open_max) - { - // First time through. - errno = 0; - if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) - { - if (0 == errno) - { - // Indeterminate. - open_max = OPEN_MAX_GUESS; - } - else - { - LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL; - } - } - } - return open_max; -} -#endif - -void LLOSInfo::stream(std::ostream& s) const -{ - s << mOSString; -} - -const std::string& LLOSInfo::getOSString() const -{ - return mOSString; -} - -const std::string& LLOSInfo::getOSStringSimple() const -{ - return mOSStringSimple; -} - -const std::string& LLOSInfo::getOSVersionString() const -{ - return mOSVersionString; -} - -const S32 LLOSInfo::getOSBitness() const -{ - return mOSBitness; -} - -//static -U32 LLOSInfo::getProcessVirtualSizeKB() -{ - U32 virtual_size = 0; -#if LL_LINUX -# define STATUS_SIZE 2048 - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; - - // All these guys return numbers in KB - char *memp = strstr(buff, "VmSize:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &virtual_size); - } - fclose(status_filep); - } -#endif - return virtual_size; -} - -//static -U32 LLOSInfo::getProcessResidentSizeKB() -{ - U32 resident_size = 0; -#if LL_LINUX - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep != NULL) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; - - // All these guys return numbers in KB - char *memp = strstr(buff, "VmRSS:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &resident_size); - } - fclose(status_filep); - } -#endif - return resident_size; -} - -//static -bool LLOSInfo::is64Bit() -{ -#if LL_WINDOWS -#if defined(_WIN64) - return true; -#elif defined(_WIN32) - // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate - bool f64 = false; - return IsWow64Process(GetCurrentProcess(), &f64) && f64; -#else - return false; -#endif -#else // ! LL_WINDOWS - // we only build a 64-bit mac viewer and currently we don't build for linux at all - return true; -#endif -} - -LLCPUInfo::LLCPUInfo() -{ - std::ostringstream out; - LLProcessorInfo proc; - // proc.WriteInfoTextFile("procInfo.txt"); - mHasSSE = proc.hasSSE(); - mHasSSE2 = proc.hasSSE2(); - mHasSSE3 = proc.hasSSE3(); - mHasSSE3S = proc.hasSSE3S(); - mHasSSE41 = proc.hasSSE41(); - mHasSSE42 = proc.hasSSE42(); - mHasSSE4a = proc.hasSSE4a(); - mHasAltivec = proc.hasAltivec(); - mCPUMHz = (F64)proc.getCPUFrequency(); - mFamily = proc.getCPUFamilyName(); - mCPUString = "Unknown"; - - out << proc.getCPUBrandName(); - if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check - { - out << " (" << mCPUMHz << " MHz)"; - } - mCPUString = out.str(); - LLStringUtil::trim(mCPUString); - - if (mHasSSE) - { - mSSEVersions.append("1"); - } - if (mHasSSE2) - { - mSSEVersions.append("2"); - } - if (mHasSSE3) - { - mSSEVersions.append("3"); - } - if (mHasSSE3S) - { - mSSEVersions.append("3S"); - } - if (mHasSSE41) - { - mSSEVersions.append("4.1"); - } - if (mHasSSE42) - { - mSSEVersions.append("4.2"); - } - if (mHasSSE4a) - { - mSSEVersions.append("4a"); - } -} - -bool LLCPUInfo::hasAltivec() const -{ - return mHasAltivec; -} - -bool LLCPUInfo::hasSSE() const -{ - return mHasSSE; -} - -bool LLCPUInfo::hasSSE2() const -{ - return mHasSSE2; -} - -bool LLCPUInfo::hasSSE3() const -{ - return mHasSSE3; -} - -bool LLCPUInfo::hasSSE3S() const -{ - return mHasSSE3S; -} - -bool LLCPUInfo::hasSSE41() const -{ - return mHasSSE41; -} - -bool LLCPUInfo::hasSSE42() const -{ - return mHasSSE42; -} - -bool LLCPUInfo::hasSSE4a() const -{ - return mHasSSE4a; -} - -F64 LLCPUInfo::getMHz() const -{ - return mCPUMHz; -} - -std::string LLCPUInfo::getCPUString() const -{ - return mCPUString; -} - -const LLSD& LLCPUInfo::getSSEVersions() const -{ - return mSSEVersions; -} - -void LLCPUInfo::stream(std::ostream& s) const -{ - // gather machine information. - s << LLProcessorInfo().getCPUFeatureDescription(); - - // These are interesting as they reflect our internal view of the - // CPU's attributes regardless of platform - s << "->mHasSSE: " << (U32)mHasSSE << std::endl; - s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl; - s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl; - s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl; - s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl; - s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl; - s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl; - s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; - s << "->mCPUMHz: " << mCPUMHz << std::endl; - s << "->mCPUString: " << mCPUString << std::endl; -} - -// Helper class for LLMemoryInfo: accumulate stats in the form we store for -// LLMemoryInfo::getStatsMap(). -class Stats -{ -public: - Stats(): - mStats(LLSD::emptyMap()) - {} - - // Store every integer type as LLSD::Integer. - template - void add(const LLSD::String& name, const T& value, - typename boost::enable_if >::type* = 0) - { - mStats[name] = LLSD::Integer(value); - } - - // Store every floating-point type as LLSD::Real. - template - void add(const LLSD::String& name, const T& value, - typename boost::enable_if >::type* = 0) - { - mStats[name] = LLSD::Real(value); - } - - // Hope that LLSD::Date values are sufficiently unambiguous. - void add(const LLSD::String& name, const LLSD::Date& value) - { - mStats[name] = value; - } - - LLSD get() const { return mStats; } - -private: - LLSD mStats; -}; - -LLMemoryInfo::LLMemoryInfo() -{ - refresh(); -} - -#if LL_WINDOWS -static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) -{ - // Moved this here from llfloaterabout.cpp - - //! \bug - // For some reason, the reported amount of memory is always wrong. - // The original adjustment assumes it's always off by one meg, however - // errors of as much as 2520 KB have been observed in the value - // returned from the GetMemoryStatusEx function. Here we keep the - // original adjustment from llfoaterabout.cpp until this can be - // fixed somehow. - inKB += U32Megabytes(1); - - return inKB; -} -#endif - -#if LL_DARWIN -// static -U32Kilobytes LLMemoryInfo::getHardwareMemSize() -{ - // This might work on Linux as well. Someone check... - uint64_t phys = 0; - int mib[2] = { CTL_HW, HW_MEMSIZE }; - - size_t len = sizeof(phys); - sysctl(mib, 2, &phys, &len, NULL, 0); - - return U64Bytes(phys); -} -#endif - -U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const -{ -#if LL_WINDOWS - return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); - -#elif LL_DARWIN - return getHardwareMemSize(); - -#elif LL_LINUX - U64 phys = 0; - phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); - return U64Bytes(phys); - -#else - return 0; - -#endif -} - -//static -void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) -{ -#if LL_WINDOWS - // Sigh, this shouldn't be a static method, then we wouldn't have to - // reload this data separately from refresh() - LLSD statsMap(loadStatsMap()); - - avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); - avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger(); - -#elif LL_DARWIN - // mStatsMap is derived from vm_stat, look for (e.g.) "kb free": - // $ vm_stat - // Mach Virtual Memory Statistics: (page size of 4096 bytes) - // Pages free: 462078. - // Pages active: 142010. - // Pages inactive: 220007. - // Pages wired down: 159552. - // "Translation faults": 220825184. - // Pages copy-on-write: 2104153. - // Pages zero filled: 167034876. - // Pages reactivated: 65153. - // Pageins: 2097212. - // Pageouts: 41759. - // Object cache: 841598 hits of 7629869 lookups (11% hit rate) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; - -#elif LL_LINUX - // mStatsMap is derived from MEMINFO_FILE: - // $ cat /proc/meminfo - // MemTotal: 4108424 kB - // MemFree: 1244064 kB - // Buffers: 85164 kB - // Cached: 1990264 kB - // SwapCached: 0 kB - // Active: 1176648 kB - // Inactive: 1427532 kB - // Active(anon): 529152 kB - // Inactive(anon): 15924 kB - // Active(file): 647496 kB - // Inactive(file): 1411608 kB - // Unevictable: 16 kB - // Mlocked: 16 kB - // HighTotal: 3266316 kB - // HighFree: 721308 kB - // LowTotal: 842108 kB - // LowFree: 522756 kB - // SwapTotal: 6384632 kB - // SwapFree: 6384632 kB - // Dirty: 28 kB - // Writeback: 0 kB - // AnonPages: 528820 kB - // Mapped: 89472 kB - // Shmem: 16324 kB - // Slab: 159624 kB - // SReclaimable: 145168 kB - // SUnreclaim: 14456 kB - // KernelStack: 2560 kB - // PageTables: 5560 kB - // NFS_Unstable: 0 kB - // Bounce: 0 kB - // WritebackTmp: 0 kB - // CommitLimit: 8438844 kB - // Committed_AS: 1271596 kB - // VmallocTotal: 122880 kB - // VmallocUsed: 65252 kB - // VmallocChunk: 52356 kB - // HardwareCorrupted: 0 kB - // HugePages_Total: 0 - // HugePages_Free: 0 - // HugePages_Rsvd: 0 - // HugePages_Surp: 0 - // Hugepagesize: 2048 kB - // DirectMap4k: 434168 kB - // DirectMap2M: 477184 kB - // (could also run 'free', but easier to read a file than run a program) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; - -#else - //do not know how to collect available memory info for other systems. - //leave it blank here for now. - - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; -#endif -} - -void LLMemoryInfo::stream(std::ostream& s) const -{ - // We want these memory stats to be easy to grep from the log, along with - // the timestamp. So preface each line with the timestamp and a - // distinctive marker. Without that, we'd have to search the log for the - // introducer line, then read subsequent lines, etc... - std::string pfx(LLError::utcTime() + " "); - - // Max key length - size_t key_width(0); - for (const auto& [key, value] : inMap(mStatsMap)) - { - size_t len(key.length()); - if (len > key_width) - { - key_width = len; - } - } - - // Now stream stats - for (const auto& [key, value] : inMap(mStatsMap)) - { - s << pfx << std::setw(narrow(key_width+1)) << (key + ':') << ' '; - if (value.isInteger()) - s << std::setw(12) << value.asInteger(); - else if (value.isReal()) - s << std::fixed << std::setprecision(1) << value.asReal(); - else if (value.isDate()) - value.asDate().toStream(s); - else - s << value; // just use default LLSD formatting - s << std::endl; - } -} - -LLSD LLMemoryInfo::getStatsMap() const -{ - return mStatsMap; -} - -LLMemoryInfo& LLMemoryInfo::refresh() -{ - LL_PROFILE_ZONE_SCOPED - mStatsMap = loadStatsMap(); - - LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; - LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); - LL_ENDL; - - return *this; -} - -LLSD LLMemoryInfo::loadStatsMap() -{ - LL_PROFILE_ZONE_SCOPED; - - // This implementation is derived from stream() code (as of 2011-06-29). - Stats stats; - - // associate timestamp for analysis over time - stats.add("timestamp", LLDate::now()); - -#if LL_WINDOWS - MEMORYSTATUSEX state; - state.dwLength = sizeof(state); - GlobalMemoryStatusEx(&state); - - DWORDLONG div = 1024; - - stats.add("Percent Memory use", state.dwMemoryLoad/div); - stats.add("Total Physical KB", state.ullTotalPhys/div); - stats.add("Avail Physical KB", state.ullAvailPhys/div); - stats.add("Total page KB", state.ullTotalPageFile/div); - stats.add("Avail page KB", state.ullAvailPageFile/div); - stats.add("Total Virtual KB", state.ullTotalVirtual/div); - stats.add("Avail Virtual KB", state.ullAvailVirtual/div); - - // SL-12122 - Call to GetPerformanceInfo() was removed here. Took - // on order of 10 ms, causing unacceptable frame time spike every - // second, and results were never used. If this is needed in the - // future, must find a way to avoid frame time impact (e.g. move - // to another thread, call much less often). - - PROCESS_MEMORY_COUNTERS_EX pmem; - pmem.cb = sizeof(pmem); - // GetProcessMemoryInfo() is documented to accept either - // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably - // using the redundant size info to distinguish. But its prototype - // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a - // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the - // pointer. - GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); - - stats.add("Page Fault Count", pmem.PageFaultCount); - stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); - stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div); - stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div); - stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div); - stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div); - stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div); - stats.add("PagefileUsage KB", pmem.PagefileUsage/div); - stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div); - stats.add("PrivateUsage KB", pmem.PrivateUsage/div); - -#elif LL_DARWIN - - const vm_size_t pagekb(vm_page_size / 1024); - - // - // Collect the vm_stat's - // - - { - vm_statistics64_data_t vmstat; - mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT; - - if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; - } - else - { - stats.add("Pages free KB", pagekb * vmstat.free_count); - stats.add("Pages active KB", pagekb * vmstat.active_count); - stats.add("Pages inactive KB", pagekb * vmstat.inactive_count); - stats.add("Pages wired KB", pagekb * vmstat.wire_count); - - stats.add("Pages zero fill", vmstat.zero_fill_count); - stats.add("Page reactivations", vmstat.reactivations); - stats.add("Page-ins", vmstat.pageins); - stats.add("Page-outs", vmstat.pageouts); - - stats.add("Faults", vmstat.faults); - stats.add("Faults copy-on-write", vmstat.cow_faults); - - stats.add("Cache lookups", vmstat.lookups); - stats.add("Cache hits", vmstat.hits); - - stats.add("Page purgeable count", vmstat.purgeable_count); - stats.add("Page purges", vmstat.purges); - - stats.add("Page speculative reads", vmstat.speculative_count); - } - } - - // - // Collect the misc task info - // - - { - task_events_info_data_t taskinfo; - unsigned taskinfoSize = sizeof(taskinfo); - - if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; - } - else - { - stats.add("Task page-ins", taskinfo.pageins); - stats.add("Task copy-on-write faults", taskinfo.cow_faults); - stats.add("Task messages sent", taskinfo.messages_sent); - stats.add("Task messages received", taskinfo.messages_received); - stats.add("Task mach system call count", taskinfo.syscalls_mach); - stats.add("Task unix system call count", taskinfo.syscalls_unix); - stats.add("Task context switch count", taskinfo.csw); - } - } - - // - // Collect the basic task info - // - - { - mach_task_basic_info_data_t taskinfo; - mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT; - if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; - } - else - { - stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024); - stats.add("Basic resident memory KB", taskinfo.resident_size / 1024); - stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024); - stats.add("Basic new thread policy", taskinfo.policy); - stats.add("Basic suspend count", taskinfo.suspend_count); - } - } - -#elif LL_LINUX - std::ifstream meminfo(MEMINFO_FILE); - if (meminfo.is_open()) - { - // MemTotal: 4108424 kB - // MemFree: 1244064 kB - // Buffers: 85164 kB - // Cached: 1990264 kB - // SwapCached: 0 kB - // Active: 1176648 kB - // Inactive: 1427532 kB - // ... - // VmallocTotal: 122880 kB - // VmallocUsed: 65252 kB - // VmallocChunk: 52356 kB - // HardwareCorrupted: 0 kB - // HugePages_Total: 0 - // HugePages_Free: 0 - // HugePages_Rsvd: 0 - // HugePages_Surp: 0 - // Hugepagesize: 2048 kB - // DirectMap4k: 434168 kB - // DirectMap2M: 477184 kB - - // Intentionally don't pass the boost::no_except flag. This - // boost::regex object is constructed with a string literal, so it - // should be valid every time. If it becomes invalid, we WANT an - // exception, hopefully even before the dev checks in. - boost::regex stat_rx("(.+): +([0-9]+)( kB)?"); - boost::smatch matched; - - std::string line; - while (std::getline(meminfo, line)) - { - LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; - if (ll_regex_match(line, matched, stat_rx)) - { - // e.g. "MemTotal: 4108424 kB" - LLSD::String key(matched[1].first, matched[1].second); - LLSD::String value_str(matched[2].first, matched[2].second); - LLSD::Integer value(0); - try - { - value = boost::lexical_cast(value_str); - } - catch (const boost::bad_lexical_cast&) - { - LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str - << "' in " << MEMINFO_FILE << " line: " - << line << LL_ENDL; - continue; - } - // Store this statistic. - stats.add(key, value); - } - else - { - LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: " - << line << LL_ENDL; - } - } - } - else - { - LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; - } - -#else - LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL; - -#endif - - return stats.get(); -} - -std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) -{ - info.stream(s); - return s; -} - -std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) -{ - info.stream(s); - return s; -} - -std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) -{ - info.stream(s); - return s; -} - -class FrameWatcher -{ -public: - FrameWatcher(): - // Hooking onto the "mainloop" event pump gets us one call per frame. - mConnection(LLEventPumps::instance() - .obtain("mainloop") - .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), - // Initializing mSampleStart to an invalid timestamp alerts us to skip - // trying to compute framerate on the first call. - mSampleStart(-1), - // Initializing mSampleEnd to 0 ensures that we treat the first call - // as the completion of a sample window. - mSampleEnd(0), - mFrames(0), - // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need - // the number of integer MEM_INFO_THROTTLE sample slots that will fit - // in MEM_INFO_WINDOW. Round up. - mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)), - // Initializing to F32_MAX means that the first real frame will become - // the slowest ever, which sounds like a good idea. - mSlowest(F32_MAX) - {} - - bool tick(const LLSD&) - { - F32 timestamp(mTimer.getElapsedTimeF32()); - - // Count this frame in the interval just completed. - ++mFrames; - - // Have we finished a sample window yet? - if (timestamp < mSampleEnd) - { - // no, just keep waiting - return false; - } - - // Set up for next sample window. Capture values for previous frame in - // local variables and reset data members. - U32 frames(mFrames); - F32 sampleStart(mSampleStart); - // No frames yet in next window - mFrames = 0; - // which starts right now - mSampleStart = timestamp; - // and ends MEM_INFO_THROTTLE seconds in the future - mSampleEnd = mSampleStart + MEM_INFO_THROTTLE; - - // On the very first call, that's all we can do, no framerate - // computation is possible. - if (sampleStart < 0) - { - return false; - } - - // How long did this actually take? As framerate slows, the duration - // of the frame we just finished could push us WELL beyond our desired - // sample window size. - F32 elapsed(timestamp - sampleStart); - F32 framerate(frames/elapsed); - - // Remember previous slowest framerate because we're just about to - // update it. - F32 slowest(mSlowest); - // Remember previous number of samples. - boost::circular_buffer::size_type prevSize(mSamples.size()); - - // Capture new framerate in our samples buffer. Once the buffer is - // full (after MEM_INFO_WINDOW seconds), this will displace the oldest - // sample. ("So they all rolled over, and one fell out...") - mSamples.push_back(framerate); - - // Calculate the new minimum framerate. I know of no way to update a - // rolling minimum without ever rescanning the buffer. But since there - // are only a few tens of items in this buffer, rescanning it is - // probably cheaper (and certainly easier to reason about) than - // attempting to optimize away some of the scans. - mSlowest = framerate; // pick an arbitrary entry to start - for (boost::circular_buffer::const_iterator si(mSamples.begin()), send(mSamples.end()); - si != send; ++si) - { - if (*si < mSlowest) - { - mSlowest = *si; - } - } - - // We're especially interested in memory as framerate drops. Only log - // when framerate drops below the slowest framerate we remember. - // (Should always be true for the end of the very first sample - // window.) - if (framerate >= slowest) - { - return false; - } - // Congratulations, we've hit a new low. :-P - - LL_INFOS("FrameWatcher") << ' '; - if (! prevSize) - { - LL_CONT << "initial framerate "; - } - else - { - LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE) - << " seconds "; - } - - auto precision = LL_CONT.precision(); - - LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' - << LLMemoryInfo(); - - LL_CONT.precision(precision); - LL_CONT << LL_ENDL; - return false; - } - -private: - // Storing the connection in an LLTempBoundListener ensures it will be - // disconnected when we're destroyed. - LLTempBoundListener mConnection; - // Track elapsed time - LLTimer mTimer; - // Some of what you see here is in fact redundant with functionality you - // can get from LLTimer. Unfortunately the LLTimer API is missing the - // feature we need: has at least the stated interval elapsed, and if so, - // exactly how long has passed? So we have to do it by hand, sigh. - // Time at start, end of sample window - F32 mSampleStart, mSampleEnd; - // Frames this sample window - U32 mFrames; - // Sliding window of framerate samples - boost::circular_buffer mSamples; - // Slowest framerate in mSamples - F32 mSlowest; -}; - -// Need an instance of FrameWatcher before it does any good -static FrameWatcher sFrameWatcher; - -bool gunzip_file(const std::string& srcfile, const std::string& dstfile) -{ - std::string tmpfile; - const S32 UNCOMPRESS_BUFFER_SIZE = 32768; - bool retval = false; - gzFile src = NULL; - U8 buffer[UNCOMPRESS_BUFFER_SIZE]; - LLFILE *dst = NULL; - S32 bytes = 0; - tmpfile = dstfile + ".t"; -#ifdef LL_WINDOWS - llutf16string utf16filename = utf8str_to_utf16str(srcfile); - src = gzopen_w(utf16filename.c_str(), "rb"); -#else - src = gzopen(srcfile.c_str(), "rb"); -#endif - if (! src) goto err; - dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ - if (! dst) goto err; - do - { - bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); - size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst); - if (nwrit < (size_t) bytes) - { - LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL; - goto err; - } - } while(gzeof(src) == 0); - fclose(dst); - dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile, ENOENT); -#endif - if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = true; -err: - if (src != NULL) gzclose(src); - if (dst != NULL) fclose(dst); - return retval; -} - -bool gzip_file(const std::string& srcfile, const std::string& dstfile) -{ - const S32 COMPRESS_BUFFER_SIZE = 32768; - std::string tmpfile; - bool retval = false; - U8 buffer[COMPRESS_BUFFER_SIZE]; - gzFile dst = NULL; - LLFILE *src = NULL; - S32 bytes = 0; - tmpfile = dstfile + ".t"; - -#ifdef LL_WINDOWS - llutf16string utf16filename = utf8str_to_utf16str(tmpfile); - dst = gzopen_w(utf16filename.c_str(), "wb"); -#else - dst = gzopen(tmpfile.c_str(), "wb"); -#endif - - if (! dst) goto err; - src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ - if (! src) goto err; - - while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) - { - if (gzwrite(dst, buffer, bytes) <= 0) - { - LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL; - goto err; - } - } - - if (ferror(src)) - { - LL_WARNS() << "Error reading " << srcfile << LL_ENDL; - goto err; - } - - gzclose(dst); - dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile); -#endif - if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = true; - err: - if (src != NULL) fclose(src); - if (dst != NULL) gzclose(dst); - return retval; -} +/** + * @file llsys.cpp + * @brief Implementation of the basic system query functions. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +#include "linden_common.h" + +#include "llsys.h" + +#include +#ifdef LL_USESYSTEMLIBS +# include +#else +# include "zlib-ng/zlib.h" +#endif + +#include "llprocessor.h" +#include "llerrorcontrol.h" +#include "llevents.h" +#include "llformat.h" +#include "llregex.h" +#include "lltimer.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include +#include +#include +#include +#include +#include +#include +#include "llfasttimer.h" + +using namespace llsd; + +#if LL_WINDOWS +# include "llwin32headerslean.h" +# include // GetPerformanceInfo() et al. +# include +#elif LL_DARWIN +# include "llsys_objc.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#elif LL_LINUX +# include +# include +# include +# include +# include +const char MEMINFO_FILE[] = "/proc/meminfo"; +# include +#endif + +LLCPUInfo gSysCPU; + +// Don't log memory info any more often than this. It also serves as our +// framerate sample size. +static const F32 MEM_INFO_THROTTLE = 20; +// Sliding window of samples. We intentionally limit the length of time we +// remember "the slowest" framerate because framerate is very slow at login. +// If we only triggered FrameWatcher logging when the session framerate +// dropped below the login framerate, we'd have very little additional data. +static const F32 MEM_INFO_WINDOW = 10*60; + +LLOSInfo::LLOSInfo() : + mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("") +{ + +#if LL_WINDOWS + + if (IsWindows10OrGreater()) + { + mMajorVer = 10; + mMinorVer = 0; + mOSStringSimple = "Microsoft Windows 10 "; + } + else if (IsWindows8Point1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 3; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8.1 "; + } + } + else if (IsWindows8OrGreater()) + { + mMajorVer = 6; + mMinorVer = 2; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8 "; + } + } + else if (IsWindows7SP1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 SP1 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 SP1 "; + } + } + else if (IsWindows7OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 "; + } + } + else if (IsWindowsVistaSP2OrGreater()) + { + mMajorVer = 6; + mMinorVer = 0; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 SP2 "; + } + else + { + mOSStringSimple = "Microsoft Windows Vista SP2 "; + } + } + else + { + mOSStringSimple = "Unsupported Windows version "; + } + + ///get native system info if available.. + typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo + SYSTEM_INFO si; //System Info object file contains architecture info + PGNSI pGNSI; //pointer object + ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information + pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function + if (NULL != pGNSI) //check if it has failed + pGNSI(&si); //success + else + GetSystemInfo(&si); //if it fails get regular system info + //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) + + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + OSVERSIONINFOEX osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + else + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + } + + S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry + if (mMajorVer == 10) + { + DWORD cbData(sizeof(DWORD)); + DWORD data(0); + HKEY key; + LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); + if (ERROR_SUCCESS == ret_code) + { + ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast(&data), &cbData); + if (ERROR_SUCCESS == ret_code) + { + ubr = data; + } + } + + if (mBuild >= 22000) + { + // At release Windows 11 version was 10.0.22000.194 + // Windows 10 version was 10.0.19043.1266 + // There is no warranty that Win10 build won't increase, + // so until better solution is found or Microsoft updates + // SDK with IsWindows11OrGreater(), indicate "10/11" + // + // Current alternatives: + // Query WMI's Win32_OperatingSystem for OS string. Slow + // and likely to return 'compatibility' string. + // Check presence of dlls/libs or may be their version. + mOSStringSimple = "Microsoft Windows 10/11 "; + } + } + + //msdn microsoft finds 32 bit and 64 bit flavors this way.. + //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors + //of windows than this code does (in case it is needed for the future) + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit + { + mOSStringSimple += "64-bit "; + } + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) + { + mOSStringSimple += "32-bit "; + } + + mOSString = mOSStringSimple; + if (mBuild > 0) + { + mOSString += llformat("(Build %d", mBuild); + if (ubr > 0) + { + mOSString += llformat(".%d", ubr); + } + mOSString += ")"; + } + + LLStringUtil::trim(mOSStringSimple); + LLStringUtil::trim(mOSString); + +#elif LL_DARWIN + + // Initialize mOSStringSimple to something like: + // "macOS 10.13.1" + { + const char * DARWIN_PRODUCT_NAME = "macOS"; + + int64_t major_version, minor_version, bugfix_version = 0; + + if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version)) + { + mMajorVer = major_version; + mMinorVer = minor_version; + mBuild = bugfix_version; + + std::stringstream os_version_string; + os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild; + + // Put it in the OS string we are compiling + mOSStringSimple.append(os_version_string.str()); + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + } + } + + // Initialize mOSString to something like: + // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" + struct utsname un; + if(uname(&un) != -1) + { + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.sysname); + mOSString.append(" "); + mOSString.append(un.release); + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + } + else + { + mOSString = mOSStringSimple; + } + +#elif LL_LINUX + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + + const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"; + boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION); + boost::smatch matched; + + std::string glibc_version(gnu_get_libc_version()); + if ( ll_regex_match(glibc_version, matched, os_version_parse) ) + { + LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL; + + std::string version_value; + + if ( matched[1].matched ) // Major version + { + version_value.assign(matched[1].first, matched[1].second); + if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1) + { + LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but major version [1] did not match" + << LL_ENDL; + } + + if ( matched[2].matched ) // Minor version + { + version_value.assign(matched[2].first, matched[2].second); + if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1) + { + LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but minor version [1] did not match" + << LL_ENDL; + } + + if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.' + { + version_value.assign(matched[4].first, matched[4].second); + if (sscanf(version_value.c_str(), "%d", &mBuild) != 1) + { + LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_INFOS("AppInit") + << "OS build version not provided; using zero" + << LL_ENDL; + } + } + else + { + LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL; + } + +#else + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + +#endif + + std::stringstream dotted_version_string; + dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild; + mOSVersionString.append(dotted_version_string.str()); + + mOSBitness = is64Bit() ? 64 : 32; + LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL; +} + +#ifndef LL_WINDOWS +// static +long LLOSInfo::getMaxOpenFiles() +{ + const long OPEN_MAX_GUESS = 256; + +#ifdef OPEN_MAX + static long open_max = OPEN_MAX; +#else + static long open_max = 0; +#endif + + if (0 == open_max) + { + // First time through. + errno = 0; + if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) + { + if (0 == errno) + { + // Indeterminate. + open_max = OPEN_MAX_GUESS; + } + else + { + LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL; + } + } + } + return open_max; +} +#endif + +void LLOSInfo::stream(std::ostream& s) const +{ + s << mOSString; +} + +const std::string& LLOSInfo::getOSString() const +{ + return mOSString; +} + +const std::string& LLOSInfo::getOSStringSimple() const +{ + return mOSStringSimple; +} + +const std::string& LLOSInfo::getOSVersionString() const +{ + return mOSVersionString; +} + +const S32 LLOSInfo::getOSBitness() const +{ + return mOSBitness; +} + +//static +U32 LLOSInfo::getProcessVirtualSizeKB() +{ + U32 virtual_size = 0; +#if LL_LINUX +# define STATUS_SIZE 2048 + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmSize:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &virtual_size); + } + fclose(status_filep); + } +#endif + return virtual_size; +} + +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + U32 resident_size = 0; +#if LL_LINUX + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep != NULL) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmRSS:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &resident_size); + } + fclose(status_filep); + } +#endif + return resident_size; +} + +//static +bool LLOSInfo::is64Bit() +{ +#if LL_WINDOWS +#if defined(_WIN64) + return true; +#elif defined(_WIN32) + // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate + bool f64 = false; + return IsWow64Process(GetCurrentProcess(), &f64) && f64; +#else + return false; +#endif +#else // ! LL_WINDOWS + // we only build a 64-bit mac viewer and currently we don't build for linux at all + return true; +#endif +} + +LLCPUInfo::LLCPUInfo() +{ + std::ostringstream out; + LLProcessorInfo proc; + // proc.WriteInfoTextFile("procInfo.txt"); + mHasSSE = proc.hasSSE(); + mHasSSE2 = proc.hasSSE2(); + mHasSSE3 = proc.hasSSE3(); + mHasSSE3S = proc.hasSSE3S(); + mHasSSE41 = proc.hasSSE41(); + mHasSSE42 = proc.hasSSE42(); + mHasSSE4a = proc.hasSSE4a(); + mHasAltivec = proc.hasAltivec(); + mCPUMHz = (F64)proc.getCPUFrequency(); + mFamily = proc.getCPUFamilyName(); + mCPUString = "Unknown"; + + out << proc.getCPUBrandName(); + if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check + { + out << " (" << mCPUMHz << " MHz)"; + } + mCPUString = out.str(); + LLStringUtil::trim(mCPUString); + + if (mHasSSE) + { + mSSEVersions.append("1"); + } + if (mHasSSE2) + { + mSSEVersions.append("2"); + } + if (mHasSSE3) + { + mSSEVersions.append("3"); + } + if (mHasSSE3S) + { + mSSEVersions.append("3S"); + } + if (mHasSSE41) + { + mSSEVersions.append("4.1"); + } + if (mHasSSE42) + { + mSSEVersions.append("4.2"); + } + if (mHasSSE4a) + { + mSSEVersions.append("4a"); + } +} + +bool LLCPUInfo::hasAltivec() const +{ + return mHasAltivec; +} + +bool LLCPUInfo::hasSSE() const +{ + return mHasSSE; +} + +bool LLCPUInfo::hasSSE2() const +{ + return mHasSSE2; +} + +bool LLCPUInfo::hasSSE3() const +{ + return mHasSSE3; +} + +bool LLCPUInfo::hasSSE3S() const +{ + return mHasSSE3S; +} + +bool LLCPUInfo::hasSSE41() const +{ + return mHasSSE41; +} + +bool LLCPUInfo::hasSSE42() const +{ + return mHasSSE42; +} + +bool LLCPUInfo::hasSSE4a() const +{ + return mHasSSE4a; +} + +F64 LLCPUInfo::getMHz() const +{ + return mCPUMHz; +} + +std::string LLCPUInfo::getCPUString() const +{ + return mCPUString; +} + +const LLSD& LLCPUInfo::getSSEVersions() const +{ + return mSSEVersions; +} + +void LLCPUInfo::stream(std::ostream& s) const +{ + // gather machine information. + s << LLProcessorInfo().getCPUFeatureDescription(); + + // These are interesting as they reflect our internal view of the + // CPU's attributes regardless of platform + s << "->mHasSSE: " << (U32)mHasSSE << std::endl; + s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl; + s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl; + s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl; + s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl; + s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl; + s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl; + s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; + s << "->mCPUMHz: " << mCPUMHz << std::endl; + s << "->mCPUString: " << mCPUString << std::endl; +} + +// Helper class for LLMemoryInfo: accumulate stats in the form we store for +// LLMemoryInfo::getStatsMap(). +class Stats +{ +public: + Stats(): + mStats(LLSD::emptyMap()) + {} + + // Store every integer type as LLSD::Integer. + template + void add(const LLSD::String& name, const T& value, + typename boost::enable_if >::type* = 0) + { + mStats[name] = LLSD::Integer(value); + } + + // Store every floating-point type as LLSD::Real. + template + void add(const LLSD::String& name, const T& value, + typename boost::enable_if >::type* = 0) + { + mStats[name] = LLSD::Real(value); + } + + // Hope that LLSD::Date values are sufficiently unambiguous. + void add(const LLSD::String& name, const LLSD::Date& value) + { + mStats[name] = value; + } + + LLSD get() const { return mStats; } + +private: + LLSD mStats; +}; + +LLMemoryInfo::LLMemoryInfo() +{ + refresh(); +} + +#if LL_WINDOWS +static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) +{ + // Moved this here from llfloaterabout.cpp + + //! \bug + // For some reason, the reported amount of memory is always wrong. + // The original adjustment assumes it's always off by one meg, however + // errors of as much as 2520 KB have been observed in the value + // returned from the GetMemoryStatusEx function. Here we keep the + // original adjustment from llfoaterabout.cpp until this can be + // fixed somehow. + inKB += U32Megabytes(1); + + return inKB; +} +#endif + +#if LL_DARWIN +// static +U32Kilobytes LLMemoryInfo::getHardwareMemSize() +{ + // This might work on Linux as well. Someone check... + uint64_t phys = 0; + int mib[2] = { CTL_HW, HW_MEMSIZE }; + + size_t len = sizeof(phys); + sysctl(mib, 2, &phys, &len, NULL, 0); + + return U64Bytes(phys); +} +#endif + +U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const +{ +#if LL_WINDOWS + return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); + +#elif LL_DARWIN + return getHardwareMemSize(); + +#elif LL_LINUX + U64 phys = 0; + phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); + return U64Bytes(phys); + +#else + return 0; + +#endif +} + +//static +void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) +{ +#if LL_WINDOWS + // Sigh, this shouldn't be a static method, then we wouldn't have to + // reload this data separately from refresh() + LLSD statsMap(loadStatsMap()); + + avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); + avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger(); + +#elif LL_DARWIN + // mStatsMap is derived from vm_stat, look for (e.g.) "kb free": + // $ vm_stat + // Mach Virtual Memory Statistics: (page size of 4096 bytes) + // Pages free: 462078. + // Pages active: 142010. + // Pages inactive: 220007. + // Pages wired down: 159552. + // "Translation faults": 220825184. + // Pages copy-on-write: 2104153. + // Pages zero filled: 167034876. + // Pages reactivated: 65153. + // Pageins: 2097212. + // Pageouts: 41759. + // Object cache: 841598 hits of 7629869 lookups (11% hit rate) + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; + +#elif LL_LINUX + // mStatsMap is derived from MEMINFO_FILE: + // $ cat /proc/meminfo + // MemTotal: 4108424 kB + // MemFree: 1244064 kB + // Buffers: 85164 kB + // Cached: 1990264 kB + // SwapCached: 0 kB + // Active: 1176648 kB + // Inactive: 1427532 kB + // Active(anon): 529152 kB + // Inactive(anon): 15924 kB + // Active(file): 647496 kB + // Inactive(file): 1411608 kB + // Unevictable: 16 kB + // Mlocked: 16 kB + // HighTotal: 3266316 kB + // HighFree: 721308 kB + // LowTotal: 842108 kB + // LowFree: 522756 kB + // SwapTotal: 6384632 kB + // SwapFree: 6384632 kB + // Dirty: 28 kB + // Writeback: 0 kB + // AnonPages: 528820 kB + // Mapped: 89472 kB + // Shmem: 16324 kB + // Slab: 159624 kB + // SReclaimable: 145168 kB + // SUnreclaim: 14456 kB + // KernelStack: 2560 kB + // PageTables: 5560 kB + // NFS_Unstable: 0 kB + // Bounce: 0 kB + // WritebackTmp: 0 kB + // CommitLimit: 8438844 kB + // Committed_AS: 1271596 kB + // VmallocTotal: 122880 kB + // VmallocUsed: 65252 kB + // VmallocChunk: 52356 kB + // HardwareCorrupted: 0 kB + // HugePages_Total: 0 + // HugePages_Free: 0 + // HugePages_Rsvd: 0 + // HugePages_Surp: 0 + // Hugepagesize: 2048 kB + // DirectMap4k: 434168 kB + // DirectMap2M: 477184 kB + // (could also run 'free', but easier to read a file than run a program) + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; + +#else + //do not know how to collect available memory info for other systems. + //leave it blank here for now. + + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; +#endif +} + +void LLMemoryInfo::stream(std::ostream& s) const +{ + // We want these memory stats to be easy to grep from the log, along with + // the timestamp. So preface each line with the timestamp and a + // distinctive marker. Without that, we'd have to search the log for the + // introducer line, then read subsequent lines, etc... + std::string pfx(LLError::utcTime() + " "); + + // Max key length + size_t key_width(0); + for (const auto& [key, value] : inMap(mStatsMap)) + { + size_t len(key.length()); + if (len > key_width) + { + key_width = len; + } + } + + // Now stream stats + for (const auto& [key, value] : inMap(mStatsMap)) + { + s << pfx << std::setw(narrow(key_width+1)) << (key + ':') << ' '; + if (value.isInteger()) + s << std::setw(12) << value.asInteger(); + else if (value.isReal()) + s << std::fixed << std::setprecision(1) << value.asReal(); + else if (value.isDate()) + value.asDate().toStream(s); + else + s << value; // just use default LLSD formatting + s << std::endl; + } +} + +LLSD LLMemoryInfo::getStatsMap() const +{ + return mStatsMap; +} + +LLMemoryInfo& LLMemoryInfo::refresh() +{ + LL_PROFILE_ZONE_SCOPED + mStatsMap = loadStatsMap(); + + LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; + LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); + LL_ENDL; + + return *this; +} + +LLSD LLMemoryInfo::loadStatsMap() +{ + LL_PROFILE_ZONE_SCOPED; + + // This implementation is derived from stream() code (as of 2011-06-29). + Stats stats; + + // associate timestamp for analysis over time + stats.add("timestamp", LLDate::now()); + +#if LL_WINDOWS + MEMORYSTATUSEX state; + state.dwLength = sizeof(state); + GlobalMemoryStatusEx(&state); + + DWORDLONG div = 1024; + + stats.add("Percent Memory use", state.dwMemoryLoad/div); + stats.add("Total Physical KB", state.ullTotalPhys/div); + stats.add("Avail Physical KB", state.ullAvailPhys/div); + stats.add("Total page KB", state.ullTotalPageFile/div); + stats.add("Avail page KB", state.ullAvailPageFile/div); + stats.add("Total Virtual KB", state.ullTotalVirtual/div); + stats.add("Avail Virtual KB", state.ullAvailVirtual/div); + + // SL-12122 - Call to GetPerformanceInfo() was removed here. Took + // on order of 10 ms, causing unacceptable frame time spike every + // second, and results were never used. If this is needed in the + // future, must find a way to avoid frame time impact (e.g. move + // to another thread, call much less often). + + PROCESS_MEMORY_COUNTERS_EX pmem; + pmem.cb = sizeof(pmem); + // GetProcessMemoryInfo() is documented to accept either + // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably + // using the redundant size info to distinguish. But its prototype + // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a + // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the + // pointer. + GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); + + stats.add("Page Fault Count", pmem.PageFaultCount); + stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); + stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div); + stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div); + stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div); + stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div); + stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div); + stats.add("PagefileUsage KB", pmem.PagefileUsage/div); + stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div); + stats.add("PrivateUsage KB", pmem.PrivateUsage/div); + +#elif LL_DARWIN + + const vm_size_t pagekb(vm_page_size / 1024); + + // + // Collect the vm_stat's + // + + { + vm_statistics64_data_t vmstat; + mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT; + + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; + } + else + { + stats.add("Pages free KB", pagekb * vmstat.free_count); + stats.add("Pages active KB", pagekb * vmstat.active_count); + stats.add("Pages inactive KB", pagekb * vmstat.inactive_count); + stats.add("Pages wired KB", pagekb * vmstat.wire_count); + + stats.add("Pages zero fill", vmstat.zero_fill_count); + stats.add("Page reactivations", vmstat.reactivations); + stats.add("Page-ins", vmstat.pageins); + stats.add("Page-outs", vmstat.pageouts); + + stats.add("Faults", vmstat.faults); + stats.add("Faults copy-on-write", vmstat.cow_faults); + + stats.add("Cache lookups", vmstat.lookups); + stats.add("Cache hits", vmstat.hits); + + stats.add("Page purgeable count", vmstat.purgeable_count); + stats.add("Page purges", vmstat.purges); + + stats.add("Page speculative reads", vmstat.speculative_count); + } + } + + // + // Collect the misc task info + // + + { + task_events_info_data_t taskinfo; + unsigned taskinfoSize = sizeof(taskinfo); + + if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; + } + else + { + stats.add("Task page-ins", taskinfo.pageins); + stats.add("Task copy-on-write faults", taskinfo.cow_faults); + stats.add("Task messages sent", taskinfo.messages_sent); + stats.add("Task messages received", taskinfo.messages_received); + stats.add("Task mach system call count", taskinfo.syscalls_mach); + stats.add("Task unix system call count", taskinfo.syscalls_unix); + stats.add("Task context switch count", taskinfo.csw); + } + } + + // + // Collect the basic task info + // + + { + mach_task_basic_info_data_t taskinfo; + mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; + } + else + { + stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024); + stats.add("Basic resident memory KB", taskinfo.resident_size / 1024); + stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024); + stats.add("Basic new thread policy", taskinfo.policy); + stats.add("Basic suspend count", taskinfo.suspend_count); + } + } + +#elif LL_LINUX + std::ifstream meminfo(MEMINFO_FILE); + if (meminfo.is_open()) + { + // MemTotal: 4108424 kB + // MemFree: 1244064 kB + // Buffers: 85164 kB + // Cached: 1990264 kB + // SwapCached: 0 kB + // Active: 1176648 kB + // Inactive: 1427532 kB + // ... + // VmallocTotal: 122880 kB + // VmallocUsed: 65252 kB + // VmallocChunk: 52356 kB + // HardwareCorrupted: 0 kB + // HugePages_Total: 0 + // HugePages_Free: 0 + // HugePages_Rsvd: 0 + // HugePages_Surp: 0 + // Hugepagesize: 2048 kB + // DirectMap4k: 434168 kB + // DirectMap2M: 477184 kB + + // Intentionally don't pass the boost::no_except flag. This + // boost::regex object is constructed with a string literal, so it + // should be valid every time. If it becomes invalid, we WANT an + // exception, hopefully even before the dev checks in. + boost::regex stat_rx("(.+): +([0-9]+)( kB)?"); + boost::smatch matched; + + std::string line; + while (std::getline(meminfo, line)) + { + LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; + if (ll_regex_match(line, matched, stat_rx)) + { + // e.g. "MemTotal: 4108424 kB" + LLSD::String key(matched[1].first, matched[1].second); + LLSD::String value_str(matched[2].first, matched[2].second); + LLSD::Integer value(0); + try + { + value = boost::lexical_cast(value_str); + } + catch (const boost::bad_lexical_cast&) + { + LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str + << "' in " << MEMINFO_FILE << " line: " + << line << LL_ENDL; + continue; + } + // Store this statistic. + stats.add(key, value); + } + else + { + LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: " + << line << LL_ENDL; + } + } + } + else + { + LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; + } + +#else + LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL; + +#endif + + return stats.get(); +} + +std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) +{ + info.stream(s); + return s; +} + +class FrameWatcher +{ +public: + FrameWatcher(): + // Hooking onto the "mainloop" event pump gets us one call per frame. + mConnection(LLEventPumps::instance() + .obtain("mainloop") + .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), + // Initializing mSampleStart to an invalid timestamp alerts us to skip + // trying to compute framerate on the first call. + mSampleStart(-1), + // Initializing mSampleEnd to 0 ensures that we treat the first call + // as the completion of a sample window. + mSampleEnd(0), + mFrames(0), + // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need + // the number of integer MEM_INFO_THROTTLE sample slots that will fit + // in MEM_INFO_WINDOW. Round up. + mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)), + // Initializing to F32_MAX means that the first real frame will become + // the slowest ever, which sounds like a good idea. + mSlowest(F32_MAX) + {} + + bool tick(const LLSD&) + { + F32 timestamp(mTimer.getElapsedTimeF32()); + + // Count this frame in the interval just completed. + ++mFrames; + + // Have we finished a sample window yet? + if (timestamp < mSampleEnd) + { + // no, just keep waiting + return false; + } + + // Set up for next sample window. Capture values for previous frame in + // local variables and reset data members. + U32 frames(mFrames); + F32 sampleStart(mSampleStart); + // No frames yet in next window + mFrames = 0; + // which starts right now + mSampleStart = timestamp; + // and ends MEM_INFO_THROTTLE seconds in the future + mSampleEnd = mSampleStart + MEM_INFO_THROTTLE; + + // On the very first call, that's all we can do, no framerate + // computation is possible. + if (sampleStart < 0) + { + return false; + } + + // How long did this actually take? As framerate slows, the duration + // of the frame we just finished could push us WELL beyond our desired + // sample window size. + F32 elapsed(timestamp - sampleStart); + F32 framerate(frames/elapsed); + + // Remember previous slowest framerate because we're just about to + // update it. + F32 slowest(mSlowest); + // Remember previous number of samples. + boost::circular_buffer::size_type prevSize(mSamples.size()); + + // Capture new framerate in our samples buffer. Once the buffer is + // full (after MEM_INFO_WINDOW seconds), this will displace the oldest + // sample. ("So they all rolled over, and one fell out...") + mSamples.push_back(framerate); + + // Calculate the new minimum framerate. I know of no way to update a + // rolling minimum without ever rescanning the buffer. But since there + // are only a few tens of items in this buffer, rescanning it is + // probably cheaper (and certainly easier to reason about) than + // attempting to optimize away some of the scans. + mSlowest = framerate; // pick an arbitrary entry to start + for (boost::circular_buffer::const_iterator si(mSamples.begin()), send(mSamples.end()); + si != send; ++si) + { + if (*si < mSlowest) + { + mSlowest = *si; + } + } + + // We're especially interested in memory as framerate drops. Only log + // when framerate drops below the slowest framerate we remember. + // (Should always be true for the end of the very first sample + // window.) + if (framerate >= slowest) + { + return false; + } + // Congratulations, we've hit a new low. :-P + + LL_INFOS("FrameWatcher") << ' '; + if (! prevSize) + { + LL_CONT << "initial framerate "; + } + else + { + LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE) + << " seconds "; + } + + auto precision = LL_CONT.precision(); + + LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' + << LLMemoryInfo(); + + LL_CONT.precision(precision); + LL_CONT << LL_ENDL; + return false; + } + +private: + // Storing the connection in an LLTempBoundListener ensures it will be + // disconnected when we're destroyed. + LLTempBoundListener mConnection; + // Track elapsed time + LLTimer mTimer; + // Some of what you see here is in fact redundant with functionality you + // can get from LLTimer. Unfortunately the LLTimer API is missing the + // feature we need: has at least the stated interval elapsed, and if so, + // exactly how long has passed? So we have to do it by hand, sigh. + // Time at start, end of sample window + F32 mSampleStart, mSampleEnd; + // Frames this sample window + U32 mFrames; + // Sliding window of framerate samples + boost::circular_buffer mSamples; + // Slowest framerate in mSamples + F32 mSlowest; +}; + +// Need an instance of FrameWatcher before it does any good +static FrameWatcher sFrameWatcher; + +bool gunzip_file(const std::string& srcfile, const std::string& dstfile) +{ + std::string tmpfile; + const S32 UNCOMPRESS_BUFFER_SIZE = 32768; + bool retval = false; + gzFile src = NULL; + U8 buffer[UNCOMPRESS_BUFFER_SIZE]; + LLFILE *dst = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(srcfile); + src = gzopen_w(utf16filename.c_str(), "rb"); +#else + src = gzopen(srcfile.c_str(), "rb"); +#endif + if (! src) goto err; + dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ + if (! dst) goto err; + do + { + bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); + size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst); + if (nwrit < (size_t) bytes) + { + LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL; + goto err; + } + } while(gzeof(src) == 0); + fclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile, ENOENT); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; +err: + if (src != NULL) gzclose(src); + if (dst != NULL) fclose(dst); + return retval; +} + +bool gzip_file(const std::string& srcfile, const std::string& dstfile) +{ + const S32 COMPRESS_BUFFER_SIZE = 32768; + std::string tmpfile; + bool retval = false; + U8 buffer[COMPRESS_BUFFER_SIZE]; + gzFile dst = NULL; + LLFILE *src = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; + +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(tmpfile); + dst = gzopen_w(utf16filename.c_str(), "wb"); +#else + dst = gzopen(tmpfile.c_str(), "wb"); +#endif + + if (! dst) goto err; + src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ + if (! src) goto err; + + while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) + { + if (gzwrite(dst, buffer, bytes) <= 0) + { + LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL; + goto err; + } + } + + if (ferror(src)) + { + LL_WARNS() << "Error reading " << srcfile << LL_ENDL; + goto err; + } + + gzclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; + err: + if (src != NULL) fclose(src); + if (dst != NULL) gzclose(dst); + return retval; +} diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 3ef1e2b528..f97d49eeb1 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -1,174 +1,174 @@ -/** - * @file llsys.h - * @brief System information debugging classes. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SYS_H -#define LL_SYS_H - -// -// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially -// the same, but query different machine subsystems. Here's how you -// use an LLCPUInfo object: -// -// LLCPUInfo info; -// LL_INFOS() << info << LL_ENDL; -// - -#include "llsd.h" -#include "llsingleton.h" -#include -#include - -class LL_COMMON_API LLOSInfo : public LLSingleton -{ - LLSINGLETON(LLOSInfo); -public: - void stream(std::ostream& s) const; - - const std::string& getOSString() const; - const std::string& getOSStringSimple() const; - - const std::string& getOSVersionString() const; - - const S32 getOSBitness() const; - - S32 mMajorVer; - S32 mMinorVer; - S32 mBuild; - -#ifndef LL_WINDOWS - static long getMaxOpenFiles(); -#endif - static bool is64Bit(); - - static U32 getProcessVirtualSizeKB(); - static U32 getProcessResidentSizeKB(); -private: - std::string mOSString; - std::string mOSStringSimple; - std::string mOSVersionString; - S32 mOSBitness; -}; - - -class LL_COMMON_API LLCPUInfo -{ -public: - LLCPUInfo(); - void stream(std::ostream& s) const; - - std::string getCPUString() const; - const LLSD& getSSEVersions() const; - - bool hasAltivec() const; - bool hasSSE() const; - bool hasSSE2() const; - bool hasSSE3() const; - bool hasSSE3S() const; - bool hasSSE41() const; - bool hasSSE42() const; - bool hasSSE4a() const; - F64 getMHz() const; - - // Family is "AMD Duron" or "Intel Pentium Pro" - const std::string& getFamily() const { return mFamily; } - -private: - bool mHasSSE; - bool mHasSSE2; - bool mHasSSE3; - bool mHasSSE3S; - bool mHasSSE41; - bool mHasSSE42; - bool mHasSSE4a; - bool mHasAltivec; - F64 mCPUMHz; - std::string mFamily; - std::string mCPUString; - LLSD mSSEVersions; -}; - -//============================================================================= -// -// CLASS LLMemoryInfo - -class LL_COMMON_API LLMemoryInfo - -/*! @brief Class to query the memory subsystem - - @details - Here's how you use an LLMemoryInfo: - - LLMemoryInfo info; -
LL_INFOS() << info << LL_ENDL; -*/ -{ -public: - LLMemoryInfo(); ///< Default constructor - void stream(std::ostream& s) const; ///< output text info to s - - U32Kilobytes getPhysicalMemoryKB() const; -#if LL_DARWIN - static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. -#endif - - //get the available memory infomation in KiloBytes. - static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); - - // Retrieve a map of memory statistics. The keys of the map are platform- - // dependent. The values are in kilobytes to try to avoid integer overflow. - LLSD getStatsMap() const; - - // Re-fetch memory data (as reported by stream() and getStatsMap()) from the - // system. Normally this is fetched at construction time. Return (*this) - // to permit usage of the form: - // @code - // LLMemoryInfo info; - // ... - // info.refresh().getStatsMap(); - // @endcode - LLMemoryInfo& refresh(); - -private: - // set mStatsMap - static LLSD loadStatsMap(); - - // Memory stats for getStatsMap(). - LLSD mStatsMap; -}; - - -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); - -// gunzip srcfile into dstfile. Returns false on error. -bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); -// gzip srcfile into dstfile. Returns false on error. -bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); - -extern LL_COMMON_API LLCPUInfo gSysCPU; - -#endif // LL_LLSYS_H +/** + * @file llsys.h + * @brief System information debugging classes. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SYS_H +#define LL_SYS_H + +// +// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially +// the same, but query different machine subsystems. Here's how you +// use an LLCPUInfo object: +// +// LLCPUInfo info; +// LL_INFOS() << info << LL_ENDL; +// + +#include "llsd.h" +#include "llsingleton.h" +#include +#include + +class LL_COMMON_API LLOSInfo : public LLSingleton +{ + LLSINGLETON(LLOSInfo); +public: + void stream(std::ostream& s) const; + + const std::string& getOSString() const; + const std::string& getOSStringSimple() const; + + const std::string& getOSVersionString() const; + + const S32 getOSBitness() const; + + S32 mMajorVer; + S32 mMinorVer; + S32 mBuild; + +#ifndef LL_WINDOWS + static long getMaxOpenFiles(); +#endif + static bool is64Bit(); + + static U32 getProcessVirtualSizeKB(); + static U32 getProcessResidentSizeKB(); +private: + std::string mOSString; + std::string mOSStringSimple; + std::string mOSVersionString; + S32 mOSBitness; +}; + + +class LL_COMMON_API LLCPUInfo +{ +public: + LLCPUInfo(); + void stream(std::ostream& s) const; + + std::string getCPUString() const; + const LLSD& getSSEVersions() const; + + bool hasAltivec() const; + bool hasSSE() const; + bool hasSSE2() const; + bool hasSSE3() const; + bool hasSSE3S() const; + bool hasSSE41() const; + bool hasSSE42() const; + bool hasSSE4a() const; + F64 getMHz() const; + + // Family is "AMD Duron" or "Intel Pentium Pro" + const std::string& getFamily() const { return mFamily; } + +private: + bool mHasSSE; + bool mHasSSE2; + bool mHasSSE3; + bool mHasSSE3S; + bool mHasSSE41; + bool mHasSSE42; + bool mHasSSE4a; + bool mHasAltivec; + F64 mCPUMHz; + std::string mFamily; + std::string mCPUString; + LLSD mSSEVersions; +}; + +//============================================================================= +// +// CLASS LLMemoryInfo + +class LL_COMMON_API LLMemoryInfo + +/*! @brief Class to query the memory subsystem + + @details + Here's how you use an LLMemoryInfo: + + LLMemoryInfo info; +
LL_INFOS() << info << LL_ENDL; +*/ +{ +public: + LLMemoryInfo(); ///< Default constructor + void stream(std::ostream& s) const; ///< output text info to s + + U32Kilobytes getPhysicalMemoryKB() const; +#if LL_DARWIN + static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. +#endif + + //get the available memory infomation in KiloBytes. + static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); + + // Retrieve a map of memory statistics. The keys of the map are platform- + // dependent. The values are in kilobytes to try to avoid integer overflow. + LLSD getStatsMap() const; + + // Re-fetch memory data (as reported by stream() and getStatsMap()) from the + // system. Normally this is fetched at construction time. Return (*this) + // to permit usage of the form: + // @code + // LLMemoryInfo info; + // ... + // info.refresh().getStatsMap(); + // @endcode + LLMemoryInfo& refresh(); + +private: + // set mStatsMap + static LLSD loadStatsMap(); + + // Memory stats for getStatsMap(). + LLSD mStatsMap; +}; + + +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); + +// gunzip srcfile into dstfile. Returns false on error. +bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); +// gzip srcfile into dstfile. Returns false on error. +bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); + +extern LL_COMMON_API LLCPUInfo gSysCPU; + +#endif // LL_LLSYS_H diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index deb1df640c..cf1b51e0aa 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -1,472 +1,472 @@ -/** - * @file llthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llapr.h" - -#include "apr_portable.h" - -#include "llthread.h" -#include "llmutex.h" - -#include "lltimer.h" -#include "lltrace.h" -#include "lltracethreadrecorder.h" -#include "llexception.h" - -#if LL_LINUX -#include -#endif - - -#ifdef LL_WINDOWS - -const DWORD MS_VC_EXCEPTION=0x406D1388; - -#pragma pack(push,8) -typedef struct tagTHREADNAME_INFO -{ - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. -} THREADNAME_INFO; -#pragma pack(pop) - -void set_thread_name( DWORD dwThreadID, const char* threadName) -{ - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = threadName; - info.dwThreadID = dwThreadID; - info.dwFlags = 0; - - __try - { - ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info ); - } - __except(EXCEPTION_CONTINUE_EXECUTION) - { - } -} -#endif - - -//---------------------------------------------------------------------------- -// Usage: -// void run_func(LLThread* thread) -// { -// } -// LLThread* thread = new LLThread(); -// thread->run(run_func); -// ... -// thread->setQuitting(); -// while(!timeout) -// { -// if (thread->isStopped()) -// { -// delete thread; -// break; -// } -// } -// -//---------------------------------------------------------------------------- -namespace -{ - - LLThread::id_t main_thread() - { - // Using a function-static variable to identify the main thread - // requires that control reach here from the main thread before it - // reaches here from any other thread. We simply trust that whichever - // thread gets here first is the main thread. - static LLThread::id_t s_thread_id = LLThread::currentID(); - return s_thread_id; - } - -} // anonymous namespace - -LL_COMMON_API bool on_main_thread() -{ - return (LLThread::currentID() == main_thread()); -} - -LL_COMMON_API bool assert_main_thread() -{ - auto curr = LLThread::currentID(); - auto main = main_thread(); - if (curr == main) - return true; - - LL_WARNS() << "Illegal execution from thread id " << curr - << " outside main thread " << main << LL_ENDL; - return false; -} - -// this function has become moot -void LLThread::registerThreadID() {} - -// -// Handed to the APR thread creation function -// -void LLThread::threadRun() -{ -#ifdef LL_WINDOWS - set_thread_name(-1, mName.c_str()); - -#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) - HANDLE hThread = GetCurrentThread(); - if (hThread) - { - SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); - } -#endif - -#endif - - LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); - - // this is the first point at which we're actually running in the new thread - mID = currentID(); - - // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread - mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder()); - - // Run the user supplied function - do - { - try - { - run(); - } - catch (const LLContinueError &e) - { - LL_WARNS("THREAD") << "ContinueException on thread '" << mName << - "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; - //output possible call stacks to log file. - LLError::LLCallStacks::print(); - - LOG_UNHANDLED_EXCEPTION("LLThread"); - continue; - } - break; - - } while (true); - - //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL; - - - delete mRecorder; - mRecorder = NULL; - - // We're done with the run function, this thread is done executing now. - //NB: we are using this flag to sync across threads...we really need memory barriers here - // Todo: add LLMutex per thread instead of flag? - // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere. - mStatus = STOPPED; -} - -LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : - mPaused(false), - mName(name), - mThreadp(NULL), - mStatus(STOPPED), - mRecorder(NULL) -{ - mRunCondition = new LLCondition(); - mDataLock = new LLMutex(); - mLocalAPRFilePoolp = NULL ; -} - - -LLThread::~LLThread() -{ - shutdown(); - - if (isCrashed()) - { - LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL; - } - - if(mLocalAPRFilePoolp) - { - delete mLocalAPRFilePoolp ; - mLocalAPRFilePoolp = NULL ; - } -} - -void LLThread::shutdown() -{ - if (isCrashed()) - { - LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL; - } - - // Warning! If you somehow call the thread destructor from itself, - // the thread will die in an unclean fashion! - if (mThreadp) - { - if (!isStopped()) - { - // The thread isn't already stopped - // First, set the flag that indicates that we're ready to die - setQuitting(); - - //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL; - // Now wait a bit for the thread to exit - // It's unclear whether I should even bother doing this - this destructor - // should never get called unless we're already stopped, really... - S32 counter = 0; - const S32 MAX_WAIT = 600; - while (counter < MAX_WAIT) - { - if (isStopped()) - { - break; - } - // Sleep for a tenth of a second - ms_sleep(100); - yield(); - counter++; - } - } - - if (!isStopped()) - { - // This thread just wouldn't stop, even though we gave it time - //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL; - // Put a stake in its heart. (A very hostile method to force a thread to quit) -#if LL_WINDOWS - TerminateThread(mNativeHandle, 0); -#else - pthread_cancel(mNativeHandle); -#endif - - delete mRecorder; - mRecorder = NULL; - mStatus = STOPPED; - return; - } - mThreadp = NULL; - } - - delete mRunCondition; - mRunCondition = NULL; - - delete mDataLock; - mDataLock = NULL; - - if (mRecorder) - { - // missed chance to properly shut down recorder (needs to be done in thread context) - // probably due to abnormal thread termination - // so just leak it and remove it from parent - LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder); - } -} - - -void LLThread::start() -{ - llassert(isStopped()); - - // Set thread state to running - mStatus = RUNNING; - - try - { - mThreadp = new std::thread(std::bind(&LLThread::threadRun, this)); - mNativeHandle = mThreadp->native_handle(); - } - catch (std::system_error& ex) - { - mStatus = STOPPED; - LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL; - } - -} - -//============================================================================ -// Called from MAIN THREAD. - -// Request that the thread pause/resume. -// The thread will pause when (and if) it calls checkPause() -void LLThread::pause() -{ - if (!mPaused) - { - // this will cause the thread to stop execution as soon as checkPause() is called - mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread - } -} - -void LLThread::unpause() -{ - if (mPaused) - { - mPaused = 0; - } - - wake(); // wake up the thread if necessary -} - -// virtual predicate function -- returns true if the thread should wake up, false if it should sleep. -bool LLThread::runCondition(void) -{ - // by default, always run. Handling of pause/unpause is done regardless of this function's result. - return true; -} - -//============================================================================ -// Called from run() (CHILD THREAD). -// Stop thread execution if requested until unpaused. -void LLThread::checkPause() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); - - // This is in a while loop because the pthread API allows for spurious wakeups. - while(shouldSleep()) - { - mDataLock->unlock(); - mRunCondition->wait(); // unlocks mRunCondition - mDataLock->lock(); - // mRunCondition is locked when the thread wakes up - } - - mDataLock->unlock(); -} - -//============================================================================ - -void LLThread::setQuitting() -{ - mDataLock->lock(); - if (mStatus == RUNNING) - { - mStatus = QUITTING; - } - // It's only safe to remove mRunCondition if all locked threads were notified - mRunCondition->broadcast(); - mDataLock->unlock(); -} - -// static -LLThread::id_t LLThread::currentID() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - return std::this_thread::get_id(); -} - -// static -void LLThread::yield() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - std::this_thread::yield(); -} - -void LLThread::wake() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); - if(!shouldSleep()) - { - mRunCondition->signal(); - } - mDataLock->unlock(); -} - -void LLThread::wakeLocked() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(!shouldSleep()) - { - mRunCondition->signal(); - } -} - -void LLThread::lockData() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); -} - -void LLThread::unlockData() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->unlock(); -} - -//============================================================================ - -//---------------------------------------------------------------------------- - -//static -LLMutex* LLThreadSafeRefCount::sMutex = 0; - -//static -void LLThreadSafeRefCount::initThreadSafeRefCount() -{ - if (!sMutex) - { - sMutex = new LLMutex(); - } -} - -//static -void LLThreadSafeRefCount::cleanupThreadSafeRefCount() -{ - delete sMutex; - sMutex = NULL; -} - - -//---------------------------------------------------------------------------- - -LLThreadSafeRefCount::LLThreadSafeRefCount() : - mRef(0) -{ -} - -LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src) -{ - mRef = 0; -} - -LLThreadSafeRefCount::~LLThreadSafeRefCount() -{ - if (mRef != 0) - { - LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL; - } -} - -//============================================================================ - -LLResponder::~LLResponder() -{ -} - -//============================================================================ +/** + * @file llthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llapr.h" + +#include "apr_portable.h" + +#include "llthread.h" +#include "llmutex.h" + +#include "lltimer.h" +#include "lltrace.h" +#include "lltracethreadrecorder.h" +#include "llexception.h" + +#if LL_LINUX +#include +#endif + + +#ifdef LL_WINDOWS + +const DWORD MS_VC_EXCEPTION=0x406D1388; + +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; +#pragma pack(pop) + +void set_thread_name( DWORD dwThreadID, const char* threadName) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try + { + ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + } +} +#endif + + +//---------------------------------------------------------------------------- +// Usage: +// void run_func(LLThread* thread) +// { +// } +// LLThread* thread = new LLThread(); +// thread->run(run_func); +// ... +// thread->setQuitting(); +// while(!timeout) +// { +// if (thread->isStopped()) +// { +// delete thread; +// break; +// } +// } +// +//---------------------------------------------------------------------------- +namespace +{ + + LLThread::id_t main_thread() + { + // Using a function-static variable to identify the main thread + // requires that control reach here from the main thread before it + // reaches here from any other thread. We simply trust that whichever + // thread gets here first is the main thread. + static LLThread::id_t s_thread_id = LLThread::currentID(); + return s_thread_id; + } + +} // anonymous namespace + +LL_COMMON_API bool on_main_thread() +{ + return (LLThread::currentID() == main_thread()); +} + +LL_COMMON_API bool assert_main_thread() +{ + auto curr = LLThread::currentID(); + auto main = main_thread(); + if (curr == main) + return true; + + LL_WARNS() << "Illegal execution from thread id " << curr + << " outside main thread " << main << LL_ENDL; + return false; +} + +// this function has become moot +void LLThread::registerThreadID() {} + +// +// Handed to the APR thread creation function +// +void LLThread::threadRun() +{ +#ifdef LL_WINDOWS + set_thread_name(-1, mName.c_str()); + +#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) + HANDLE hThread = GetCurrentThread(); + if (hThread) + { + SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); + } +#endif + +#endif + + LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); + + // this is the first point at which we're actually running in the new thread + mID = currentID(); + + // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread + mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder()); + + // Run the user supplied function + do + { + try + { + run(); + } + catch (const LLContinueError &e) + { + LL_WARNS("THREAD") << "ContinueException on thread '" << mName << + "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; + //output possible call stacks to log file. + LLError::LLCallStacks::print(); + + LOG_UNHANDLED_EXCEPTION("LLThread"); + continue; + } + break; + + } while (true); + + //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL; + + + delete mRecorder; + mRecorder = NULL; + + // We're done with the run function, this thread is done executing now. + //NB: we are using this flag to sync across threads...we really need memory barriers here + // Todo: add LLMutex per thread instead of flag? + // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere. + mStatus = STOPPED; +} + +LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : + mPaused(false), + mName(name), + mThreadp(NULL), + mStatus(STOPPED), + mRecorder(NULL) +{ + mRunCondition = new LLCondition(); + mDataLock = new LLMutex(); + mLocalAPRFilePoolp = NULL ; +} + + +LLThread::~LLThread() +{ + shutdown(); + + if (isCrashed()) + { + LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL; + } + + if(mLocalAPRFilePoolp) + { + delete mLocalAPRFilePoolp ; + mLocalAPRFilePoolp = NULL ; + } +} + +void LLThread::shutdown() +{ + if (isCrashed()) + { + LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL; + } + + // Warning! If you somehow call the thread destructor from itself, + // the thread will die in an unclean fashion! + if (mThreadp) + { + if (!isStopped()) + { + // The thread isn't already stopped + // First, set the flag that indicates that we're ready to die + setQuitting(); + + //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL; + // Now wait a bit for the thread to exit + // It's unclear whether I should even bother doing this - this destructor + // should never get called unless we're already stopped, really... + S32 counter = 0; + const S32 MAX_WAIT = 600; + while (counter < MAX_WAIT) + { + if (isStopped()) + { + break; + } + // Sleep for a tenth of a second + ms_sleep(100); + yield(); + counter++; + } + } + + if (!isStopped()) + { + // This thread just wouldn't stop, even though we gave it time + //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL; + // Put a stake in its heart. (A very hostile method to force a thread to quit) +#if LL_WINDOWS + TerminateThread(mNativeHandle, 0); +#else + pthread_cancel(mNativeHandle); +#endif + + delete mRecorder; + mRecorder = NULL; + mStatus = STOPPED; + return; + } + mThreadp = NULL; + } + + delete mRunCondition; + mRunCondition = NULL; + + delete mDataLock; + mDataLock = NULL; + + if (mRecorder) + { + // missed chance to properly shut down recorder (needs to be done in thread context) + // probably due to abnormal thread termination + // so just leak it and remove it from parent + LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder); + } +} + + +void LLThread::start() +{ + llassert(isStopped()); + + // Set thread state to running + mStatus = RUNNING; + + try + { + mThreadp = new std::thread(std::bind(&LLThread::threadRun, this)); + mNativeHandle = mThreadp->native_handle(); + } + catch (std::system_error& ex) + { + mStatus = STOPPED; + LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL; + } + +} + +//============================================================================ +// Called from MAIN THREAD. + +// Request that the thread pause/resume. +// The thread will pause when (and if) it calls checkPause() +void LLThread::pause() +{ + if (!mPaused) + { + // this will cause the thread to stop execution as soon as checkPause() is called + mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread + } +} + +void LLThread::unpause() +{ + if (mPaused) + { + mPaused = 0; + } + + wake(); // wake up the thread if necessary +} + +// virtual predicate function -- returns true if the thread should wake up, false if it should sleep. +bool LLThread::runCondition(void) +{ + // by default, always run. Handling of pause/unpause is done regardless of this function's result. + return true; +} + +//============================================================================ +// Called from run() (CHILD THREAD). +// Stop thread execution if requested until unpaused. +void LLThread::checkPause() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); + + // This is in a while loop because the pthread API allows for spurious wakeups. + while(shouldSleep()) + { + mDataLock->unlock(); + mRunCondition->wait(); // unlocks mRunCondition + mDataLock->lock(); + // mRunCondition is locked when the thread wakes up + } + + mDataLock->unlock(); +} + +//============================================================================ + +void LLThread::setQuitting() +{ + mDataLock->lock(); + if (mStatus == RUNNING) + { + mStatus = QUITTING; + } + // It's only safe to remove mRunCondition if all locked threads were notified + mRunCondition->broadcast(); + mDataLock->unlock(); +} + +// static +LLThread::id_t LLThread::currentID() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + return std::this_thread::get_id(); +} + +// static +void LLThread::yield() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::this_thread::yield(); +} + +void LLThread::wake() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); + if(!shouldSleep()) + { + mRunCondition->signal(); + } + mDataLock->unlock(); +} + +void LLThread::wakeLocked() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(!shouldSleep()) + { + mRunCondition->signal(); + } +} + +void LLThread::lockData() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); +} + +void LLThread::unlockData() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->unlock(); +} + +//============================================================================ + +//---------------------------------------------------------------------------- + +//static +LLMutex* LLThreadSafeRefCount::sMutex = 0; + +//static +void LLThreadSafeRefCount::initThreadSafeRefCount() +{ + if (!sMutex) + { + sMutex = new LLMutex(); + } +} + +//static +void LLThreadSafeRefCount::cleanupThreadSafeRefCount() +{ + delete sMutex; + sMutex = NULL; +} + + +//---------------------------------------------------------------------------- + +LLThreadSafeRefCount::LLThreadSafeRefCount() : + mRef(0) +{ +} + +LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src) +{ + mRef = 0; +} + +LLThreadSafeRefCount::~LLThreadSafeRefCount() +{ + if (mRef != 0) + { + LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL; + } +} + +//============================================================================ + +LLResponder::~LLResponder() +{ +} + +//============================================================================ diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 8a8451b927..a3e871661c 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -1,608 +1,608 @@ -/** - * @file lltimer.cpp - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "lltimer.h" - -#include "u64.h" - -#include -#include - -#if LL_WINDOWS -# include "llwin32headerslean.h" -#elif LL_LINUX || LL_DARWIN -# include -# include -#else -# error "architecture not supported" -#endif - -// -// Locally used constants -// -const U64 SEC_TO_MICROSEC_U64 = 1000000; - -//--------------------------------------------------------------------------- -// Globals and statics -//--------------------------------------------------------------------------- - -S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds -LLTimer* LLTimer::sTimer = NULL; - - -// -// Forward declarations -// - - -//--------------------------------------------------------------------------- -// Implementation -//--------------------------------------------------------------------------- - -#if LL_WINDOWS - - -#if 0 -void ms_sleep(U32 ms) -{ - LL_PROFILE_ZONE_SCOPED; - using TimePoint = std::chrono::steady_clock::time_point; - auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); - while (TimePoint::clock::now() < resume_time) - { - std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long - } -} - -U32 micro_sleep(U64 us, U32 max_yields) -{ - // max_yields is unused; just fiddle with it to avoid warnings. - max_yields = 0; - ms_sleep((U32)(us / 1000)); - return 0; -} - -#else - -U32 micro_sleep(U64 us, U32 max_yields) -{ - LL_PROFILE_ZONE_SCOPED -#if 0 - LARGE_INTEGER ft; - ft.QuadPart = -static_cast(us * 10); // '-' using relative time - - HANDLE timer = CreateWaitableTimer(NULL, true, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -#else - Sleep(us / 1000); -#endif - - return 0; -} - -void ms_sleep(U32 ms) -{ - LL_PROFILE_ZONE_SCOPED - micro_sleep(ms * 1000, 0); -} - -#endif - -#elif LL_LINUX || LL_DARWIN -static void _sleep_loop(struct timespec& thiswait) -{ - struct timespec nextwait; - bool sleep_more = false; - - do { - int result = nanosleep(&thiswait, &nextwait); - - // check if sleep was interrupted by a signal; unslept - // remainder was written back into 't' and we just nanosleep - // again. - sleep_more = (result == -1 && EINTR == errno); - - if (sleep_more) - { - if ( nextwait.tv_sec > thiswait.tv_sec || - (nextwait.tv_sec == thiswait.tv_sec && - nextwait.tv_nsec >= thiswait.tv_nsec) ) - { - // if the remaining time isn't actually going - // down then we're being shafted by low clock - // resolution - manually massage the sleep time - // downward. - if (nextwait.tv_nsec > 1000000) { - // lose 1ms - nextwait.tv_nsec -= 1000000; - } else { - if (nextwait.tv_sec == 0) { - // already so close to finished - sleep_more = false; - } else { - // lose up to 1ms - nextwait.tv_nsec = 0; - } - } - } - thiswait = nextwait; - } - } while (sleep_more); -} - -U32 micro_sleep(U64 us, U32 max_yields) -{ - U64 start = get_clock_count(); - // This is kernel dependent. Currently, our kernel generates software clock - // interrupts at 250 Hz (every 4,000 microseconds). - const S64 KERNEL_SLEEP_INTERVAL_US = 4000; - - // Use signed arithmetic to discover whether a sleep is even necessary. If - // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler - // promotes the difference to unsigned. If 'us' is less than half - // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely - // positive, resulting in a crazy long wait. - auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US; - if (num_sleep_intervals > 0) - { - U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1); - struct timespec thiswait; - thiswait.tv_sec = sleep_time / 1000000; - thiswait.tv_nsec = (sleep_time % 1000000) * 1000l; - _sleep_loop(thiswait); - } - - U64 current_clock = get_clock_count(); - U32 yields = 0; - while ( (yields < max_yields) - && (current_clock - start < us) ) - { - sched_yield(); - ++yields; - current_clock = get_clock_count(); - } - return yields; -} - -void ms_sleep(U32 ms) -{ - long mslong = ms; // tv_nsec is a long - struct timespec thiswait; - thiswait.tv_sec = ms / 1000; - thiswait.tv_nsec = (mslong % 1000) * 1000000l; - _sleep_loop(thiswait); -} -#else -# error "architecture not supported" -#endif - -// -// CPU clock/other clock frequency and count functions -// - -#if LL_WINDOWS -U64 get_clock_count() -{ - static bool firstTime = true; - static U64 offset; - // ensures that callers to this function never have to deal with wrap - - // QueryPerformanceCounter implementation - LARGE_INTEGER clock_count; - QueryPerformanceCounter(&clock_count); - if (firstTime) { - offset = clock_count.QuadPart; - firstTime = false; - } - return clock_count.QuadPart - offset; -} - -F64 calc_clock_frequency() -{ - __int64 freq; - QueryPerformanceFrequency((LARGE_INTEGER *) &freq); - return (F64)freq; -} -#endif // LL_WINDOWS - - -#if LL_LINUX || LL_DARWIN -// Both Linux and Mac use gettimeofday for accurate time -F64 calc_clock_frequency() -{ - return 1000000.0; // microseconds, so 1 MHz. -} - -U64 get_clock_count() -{ - // Linux clocks are in microseconds - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; -} -#endif - - -TimerInfo::TimerInfo() -: mClockFrequency(0.0), - mTotalTimeClockCount(0), - mLastTotalTimeClockCount(0) -{} - -void TimerInfo::update() -{ - mClockFrequency = calc_clock_frequency(); - mClockFrequencyInv = 1.0/mClockFrequency; - mClocksToMicroseconds = mClockFrequencyInv; -} - -TimerInfo& get_timer_info() -{ - static TimerInfo sTimerInfo; - return sTimerInfo; -} - -/////////////////////////////////////////////////////////////////////////////// - -// returns a U64 number that represents the number of -// microseconds since the Unix epoch - Jan 1, 1970 -U64MicrosecondsImplicit totalTime() -{ - U64 current_clock_count = get_clock_count(); - if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0) - { - get_timer_info().update(); - get_timer_info().mTotalTimeClockCount = current_clock_count; - -#if LL_WINDOWS - // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented) - // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to - // make in the future. - - get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency); -#endif - - // Update the last clock count - get_timer_info().mLastTotalTimeClockCount = current_clock_count; - } - else - { - if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount) - { - // No wrapping, we're all okay. - get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount; - } - else - { - // We've wrapped. Compensate correctly - get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count; - } - - // Update the last clock count - get_timer_info().mLastTotalTimeClockCount = current_clock_count; - } - - // Return the total clock tick count in microseconds. - U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds); - return time; -} - - -/////////////////////////////////////////////////////////////////////////////// - -LLTimer::LLTimer() -{ - if (!get_timer_info().mClockFrequency) - { - get_timer_info().update(); - } - - mStarted = true; - reset(); -} - -LLTimer::~LLTimer() -{} - -// static -void LLTimer::initClass() -{ - if (!sTimer) sTimer = new LLTimer; -} - -// static -void LLTimer::cleanupClass() -{ - delete sTimer; sTimer = NULL; -} - -// static -U64MicrosecondsImplicit LLTimer::getTotalTime() -{ - // simply call into the implementation function. - U64MicrosecondsImplicit total_time = totalTime(); - return total_time; -} - -// static -F64SecondsImplicit LLTimer::getTotalSeconds() -{ - return F64Microseconds(U64_to_F64(getTotalTime())); -} - -void LLTimer::reset() -{ - mLastClockCount = get_clock_count(); - mExpirationTicks = 0; -} - -/////////////////////////////////////////////////////////////////////////////// - -U64 LLTimer::getCurrentClockCount() -{ - return get_clock_count(); -} - -/////////////////////////////////////////////////////////////////////////////// - -void LLTimer::setLastClockCount(U64 current_count) -{ - mLastClockCount = current_count; -} - -/////////////////////////////////////////////////////////////////////////////// - -static -U64 getElapsedTimeAndUpdate(U64& lastClockCount) -{ - U64 current_clock_count = get_clock_count(); - U64 result; - - if (current_clock_count >= lastClockCount) - { - result = current_clock_count - lastClockCount; - } - else - { - // time has gone backward - result = 0; - } - - lastClockCount = current_clock_count; - - return result; -} - - -F64SecondsImplicit LLTimer::getElapsedTimeF64() const -{ - U64 last = mLastClockCount; - return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv; -} - -F32SecondsImplicit LLTimer::getElapsedTimeF32() const -{ - return (F32)getElapsedTimeF64(); -} - -F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64() -{ - return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv; -} - -F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() -{ - return (F32)getElapsedTimeAndResetF64(); -} - -/////////////////////////////////////////////////////////////////////////////// - -void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) -{ - mExpirationTicks = get_clock_count() - + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); -} - -F32SecondsImplicit LLTimer::getRemainingTimeF32() const -{ - U64 cur_ticks = get_clock_count(); - if (cur_ticks > mExpirationTicks) - { - return 0.0f; - } - return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv); -} - - -bool LLTimer::checkExpirationAndReset(F32 expiration) -{ - U64 cur_ticks = get_clock_count(); - if (cur_ticks < mExpirationTicks) - { - return false; - } - - mExpirationTicks = cur_ticks - + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); - return true; -} - - -bool LLTimer::hasExpired() const -{ - return get_clock_count() >= mExpirationTicks; -} - -/////////////////////////////////////////////////////////////////////////////// - -bool LLTimer::knownBadTimer() -{ - bool failed = false; - -#if LL_WINDOWS - WCHAR bad_pci_list[][10] = {L"1039:0530", - L"1039:0620", - L"10B9:0533", - L"10B9:1533", - L"1106:0596", - L"1106:0686", - L"1166:004F", - L"1166:0050", - L"8086:7110", - L"\0" - }; - - HKEY hKey = NULL; - LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, - KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); - - WCHAR name[1024]; - DWORD name_len = 1024; - FILETIME scrap; - - S32 key_num = 0; - WCHAR pci_id[10]; - - wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ - - while (nResult == ERROR_SUCCESS) - { - nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); - - if (nResult == ERROR_SUCCESS) - { - memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ - memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ - - for (S32 check = 0; bad_pci_list[check][0]; check++) - { - if (!wcscmp(pci_id, bad_pci_list[check])) - { -// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; - failed = true; - break; - } - } -// llinfo << "PCI chipset found: " << pci_id << endl; - name_len = 1024; - } - } -#endif - return(failed); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// NON-MEMBER FUNCTIONS -// -/////////////////////////////////////////////////////////////////////////////// - -time_t time_corrected() -{ - return time(NULL) + gUTCOffset; -} - - -// Is the current computer (in its current time zone) -// observing daylight savings time? -bool is_daylight_savings() -{ - time_t now = time(NULL); - - // Internal buffer to local server time - struct tm* internal_time = localtime(&now); - - // tm_isdst > 0 => daylight savings - // tm_isdst = 0 => not daylight savings - // tm_isdst < 0 => can't tell - return (internal_time->tm_isdst > 0); -} - - -struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time) -{ - S32Hours pacific_offset_hours; - if (pacific_daylight_time) - { - pacific_offset_hours = S32Hours(7); - } - else - { - pacific_offset_hours = S32Hours(8); - } - - // We subtract off the PST/PDT offset _before_ getting - // "UTC" time, because this will handle wrapping around - // for 5 AM UTC -> 10 PM PDT of the previous day. - utc_time -= S32SecondsImplicit(pacific_offset_hours); - - // Internal buffer to PST/PDT (see above) - struct tm* internal_time = gmtime(&utc_time); - - /* - // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. - if (pacific_daylight_time) - { - internal_time->tm_isdst = 1; - } - */ - - return internal_time; -} - - -void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring) -{ - U64 hours; - U64 minutes; - U64 seconds; - U64 frames; - U64 subframes; - - hours = current_time / (U64)3600000000ul; - minutes = current_time / (U64)60000000; - minutes %= 60; - seconds = current_time / (U64)1000000; - seconds %= 60; - frames = current_time / (U64)41667; - frames %= 24; - subframes = current_time / (U64)42; - subframes %= 100; - - tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); -} - - -void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring) -{ - microsecondsToTimecodeString(current_time, tcstring); -} - - +/** + * @file lltimer.cpp + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltimer.h" + +#include "u64.h" + +#include +#include + +#if LL_WINDOWS +# include "llwin32headerslean.h" +#elif LL_LINUX || LL_DARWIN +# include +# include +#else +# error "architecture not supported" +#endif + +// +// Locally used constants +// +const U64 SEC_TO_MICROSEC_U64 = 1000000; + +//--------------------------------------------------------------------------- +// Globals and statics +//--------------------------------------------------------------------------- + +S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds +LLTimer* LLTimer::sTimer = NULL; + + +// +// Forward declarations +// + + +//--------------------------------------------------------------------------- +// Implementation +//--------------------------------------------------------------------------- + +#if LL_WINDOWS + + +#if 0 +void ms_sleep(U32 ms) +{ + LL_PROFILE_ZONE_SCOPED; + using TimePoint = std::chrono::steady_clock::time_point; + auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); + while (TimePoint::clock::now() < resume_time) + { + std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long + } +} + +U32 micro_sleep(U64 us, U32 max_yields) +{ + // max_yields is unused; just fiddle with it to avoid warnings. + max_yields = 0; + ms_sleep((U32)(us / 1000)); + return 0; +} + +#else + +U32 micro_sleep(U64 us, U32 max_yields) +{ + LL_PROFILE_ZONE_SCOPED +#if 0 + LARGE_INTEGER ft; + ft.QuadPart = -static_cast(us * 10); // '-' using relative time + + HANDLE timer = CreateWaitableTimer(NULL, true, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#else + Sleep(us / 1000); +#endif + + return 0; +} + +void ms_sleep(U32 ms) +{ + LL_PROFILE_ZONE_SCOPED + micro_sleep(ms * 1000, 0); +} + +#endif + +#elif LL_LINUX || LL_DARWIN +static void _sleep_loop(struct timespec& thiswait) +{ + struct timespec nextwait; + bool sleep_more = false; + + do { + int result = nanosleep(&thiswait, &nextwait); + + // check if sleep was interrupted by a signal; unslept + // remainder was written back into 't' and we just nanosleep + // again. + sleep_more = (result == -1 && EINTR == errno); + + if (sleep_more) + { + if ( nextwait.tv_sec > thiswait.tv_sec || + (nextwait.tv_sec == thiswait.tv_sec && + nextwait.tv_nsec >= thiswait.tv_nsec) ) + { + // if the remaining time isn't actually going + // down then we're being shafted by low clock + // resolution - manually massage the sleep time + // downward. + if (nextwait.tv_nsec > 1000000) { + // lose 1ms + nextwait.tv_nsec -= 1000000; + } else { + if (nextwait.tv_sec == 0) { + // already so close to finished + sleep_more = false; + } else { + // lose up to 1ms + nextwait.tv_nsec = 0; + } + } + } + thiswait = nextwait; + } + } while (sleep_more); +} + +U32 micro_sleep(U64 us, U32 max_yields) +{ + U64 start = get_clock_count(); + // This is kernel dependent. Currently, our kernel generates software clock + // interrupts at 250 Hz (every 4,000 microseconds). + const S64 KERNEL_SLEEP_INTERVAL_US = 4000; + + // Use signed arithmetic to discover whether a sleep is even necessary. If + // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler + // promotes the difference to unsigned. If 'us' is less than half + // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely + // positive, resulting in a crazy long wait. + auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US; + if (num_sleep_intervals > 0) + { + U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1); + struct timespec thiswait; + thiswait.tv_sec = sleep_time / 1000000; + thiswait.tv_nsec = (sleep_time % 1000000) * 1000l; + _sleep_loop(thiswait); + } + + U64 current_clock = get_clock_count(); + U32 yields = 0; + while ( (yields < max_yields) + && (current_clock - start < us) ) + { + sched_yield(); + ++yields; + current_clock = get_clock_count(); + } + return yields; +} + +void ms_sleep(U32 ms) +{ + long mslong = ms; // tv_nsec is a long + struct timespec thiswait; + thiswait.tv_sec = ms / 1000; + thiswait.tv_nsec = (mslong % 1000) * 1000000l; + _sleep_loop(thiswait); +} +#else +# error "architecture not supported" +#endif + +// +// CPU clock/other clock frequency and count functions +// + +#if LL_WINDOWS +U64 get_clock_count() +{ + static bool firstTime = true; + static U64 offset; + // ensures that callers to this function never have to deal with wrap + + // QueryPerformanceCounter implementation + LARGE_INTEGER clock_count; + QueryPerformanceCounter(&clock_count); + if (firstTime) { + offset = clock_count.QuadPart; + firstTime = false; + } + return clock_count.QuadPart - offset; +} + +F64 calc_clock_frequency() +{ + __int64 freq; + QueryPerformanceFrequency((LARGE_INTEGER *) &freq); + return (F64)freq; +} +#endif // LL_WINDOWS + + +#if LL_LINUX || LL_DARWIN +// Both Linux and Mac use gettimeofday for accurate time +F64 calc_clock_frequency() +{ + return 1000000.0; // microseconds, so 1 MHz. +} + +U64 get_clock_count() +{ + // Linux clocks are in microseconds + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; +} +#endif + + +TimerInfo::TimerInfo() +: mClockFrequency(0.0), + mTotalTimeClockCount(0), + mLastTotalTimeClockCount(0) +{} + +void TimerInfo::update() +{ + mClockFrequency = calc_clock_frequency(); + mClockFrequencyInv = 1.0/mClockFrequency; + mClocksToMicroseconds = mClockFrequencyInv; +} + +TimerInfo& get_timer_info() +{ + static TimerInfo sTimerInfo; + return sTimerInfo; +} + +/////////////////////////////////////////////////////////////////////////////// + +// returns a U64 number that represents the number of +// microseconds since the Unix epoch - Jan 1, 1970 +U64MicrosecondsImplicit totalTime() +{ + U64 current_clock_count = get_clock_count(); + if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0) + { + get_timer_info().update(); + get_timer_info().mTotalTimeClockCount = current_clock_count; + +#if LL_WINDOWS + // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented) + // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to + // make in the future. + + get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency); +#endif + + // Update the last clock count + get_timer_info().mLastTotalTimeClockCount = current_clock_count; + } + else + { + if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount) + { + // No wrapping, we're all okay. + get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount; + } + else + { + // We've wrapped. Compensate correctly + get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count; + } + + // Update the last clock count + get_timer_info().mLastTotalTimeClockCount = current_clock_count; + } + + // Return the total clock tick count in microseconds. + U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds); + return time; +} + + +/////////////////////////////////////////////////////////////////////////////// + +LLTimer::LLTimer() +{ + if (!get_timer_info().mClockFrequency) + { + get_timer_info().update(); + } + + mStarted = true; + reset(); +} + +LLTimer::~LLTimer() +{} + +// static +void LLTimer::initClass() +{ + if (!sTimer) sTimer = new LLTimer; +} + +// static +void LLTimer::cleanupClass() +{ + delete sTimer; sTimer = NULL; +} + +// static +U64MicrosecondsImplicit LLTimer::getTotalTime() +{ + // simply call into the implementation function. + U64MicrosecondsImplicit total_time = totalTime(); + return total_time; +} + +// static +F64SecondsImplicit LLTimer::getTotalSeconds() +{ + return F64Microseconds(U64_to_F64(getTotalTime())); +} + +void LLTimer::reset() +{ + mLastClockCount = get_clock_count(); + mExpirationTicks = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +U64 LLTimer::getCurrentClockCount() +{ + return get_clock_count(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setLastClockCount(U64 current_count) +{ + mLastClockCount = current_count; +} + +/////////////////////////////////////////////////////////////////////////////// + +static +U64 getElapsedTimeAndUpdate(U64& lastClockCount) +{ + U64 current_clock_count = get_clock_count(); + U64 result; + + if (current_clock_count >= lastClockCount) + { + result = current_clock_count - lastClockCount; + } + else + { + // time has gone backward + result = 0; + } + + lastClockCount = current_clock_count; + + return result; +} + + +F64SecondsImplicit LLTimer::getElapsedTimeF64() const +{ + U64 last = mLastClockCount; + return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv; +} + +F32SecondsImplicit LLTimer::getElapsedTimeF32() const +{ + return (F32)getElapsedTimeF64(); +} + +F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64() +{ + return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv; +} + +F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() +{ + return (F32)getElapsedTimeAndResetF64(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) +{ + mExpirationTicks = get_clock_count() + + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); +} + +F32SecondsImplicit LLTimer::getRemainingTimeF32() const +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks > mExpirationTicks) + { + return 0.0f; + } + return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv); +} + + +bool LLTimer::checkExpirationAndReset(F32 expiration) +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks < mExpirationTicks) + { + return false; + } + + mExpirationTicks = cur_ticks + + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); + return true; +} + + +bool LLTimer::hasExpired() const +{ + return get_clock_count() >= mExpirationTicks; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool LLTimer::knownBadTimer() +{ + bool failed = false; + +#if LL_WINDOWS + WCHAR bad_pci_list[][10] = {L"1039:0530", + L"1039:0620", + L"10B9:0533", + L"10B9:1533", + L"1106:0596", + L"1106:0686", + L"1166:004F", + L"1166:0050", + L"8086:7110", + L"\0" + }; + + HKEY hKey = NULL; + LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, + KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); + + WCHAR name[1024]; + DWORD name_len = 1024; + FILETIME scrap; + + S32 key_num = 0; + WCHAR pci_id[10]; + + wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ + + while (nResult == ERROR_SUCCESS) + { + nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); + + if (nResult == ERROR_SUCCESS) + { + memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ + memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ + + for (S32 check = 0; bad_pci_list[check][0]; check++) + { + if (!wcscmp(pci_id, bad_pci_list[check])) + { +// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; + failed = true; + break; + } + } +// llinfo << "PCI chipset found: " << pci_id << endl; + name_len = 1024; + } + } +#endif + return(failed); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// NON-MEMBER FUNCTIONS +// +/////////////////////////////////////////////////////////////////////////////// + +time_t time_corrected() +{ + return time(NULL) + gUTCOffset; +} + + +// Is the current computer (in its current time zone) +// observing daylight savings time? +bool is_daylight_savings() +{ + time_t now = time(NULL); + + // Internal buffer to local server time + struct tm* internal_time = localtime(&now); + + // tm_isdst > 0 => daylight savings + // tm_isdst = 0 => not daylight savings + // tm_isdst < 0 => can't tell + return (internal_time->tm_isdst > 0); +} + + +struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time) +{ + S32Hours pacific_offset_hours; + if (pacific_daylight_time) + { + pacific_offset_hours = S32Hours(7); + } + else + { + pacific_offset_hours = S32Hours(8); + } + + // We subtract off the PST/PDT offset _before_ getting + // "UTC" time, because this will handle wrapping around + // for 5 AM UTC -> 10 PM PDT of the previous day. + utc_time -= S32SecondsImplicit(pacific_offset_hours); + + // Internal buffer to PST/PDT (see above) + struct tm* internal_time = gmtime(&utc_time); + + /* + // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. + if (pacific_daylight_time) + { + internal_time->tm_isdst = 1; + } + */ + + return internal_time; +} + + +void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring) +{ + U64 hours; + U64 minutes; + U64 seconds; + U64 frames; + U64 subframes; + + hours = current_time / (U64)3600000000ul; + minutes = current_time / (U64)60000000; + minutes %= 60; + seconds = current_time / (U64)1000000; + seconds %= 60; + frames = current_time / (U64)41667; + frames %= 24; + subframes = current_time / (U64)42; + subframes %= 100; + + tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); +} + + +void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring) +{ + microsecondsToTimecodeString(current_time, tcstring); +} + + diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 60eb007595..d79f250585 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -1,188 +1,188 @@ -/** - * @file lltimer.h - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TIMER_H -#define LL_TIMER_H - -#if LL_LINUX || LL_DARWIN -#include -#endif -#include - -#include "stdtypes.h" - -#include -#include -// units conversions -#include "llunits.h" -#ifndef USEC_PER_SEC - const U32 USEC_PER_SEC = 1000000; -#endif -const U32 SEC_PER_MIN = 60; -const U32 MIN_PER_HOUR = 60; -const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN; -const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; -const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; -const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; - -class LL_COMMON_API LLTimer -{ -public: - static LLTimer *sTimer; // global timer - -protected: - U64 mLastClockCount; - U64 mExpirationTicks; - bool mStarted; - -public: - LLTimer(); - ~LLTimer(); - - static void initClass(); - static void cleanupClass(); - - // Return a high precision number of seconds since the start of - // this application instance. - static F64SecondsImplicit getElapsedSeconds() - { - if (sTimer) - { - return sTimer->getElapsedTimeF64(); - } - else - { - return 0; - } - } - - // Return a high precision usec since epoch - static U64MicrosecondsImplicit getTotalTime(); - - // Return a high precision seconds since epoch - static F64SecondsImplicit getTotalSeconds(); - - - // MANIPULATORS - void start() { reset(); mStarted = true; } - void stop() { mStarted = false; } - void reset(); // Resets the timer - void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time - void setTimerExpirySec(F32SecondsImplicit expiration); - bool checkExpirationAndReset(F32 expiration); - bool hasExpired() const; - F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset - F64SecondsImplicit getElapsedTimeAndResetF64(); - - F32SecondsImplicit getRemainingTimeF32() const; - - static bool knownBadTimer(); - - // ACCESSORS - F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds - F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds - - bool getStarted() const { return mStarted; } - - - static U64 getCurrentClockCount(); // Returns the raw clockticks -}; - -// -// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. -// -struct TimerInfo -{ - TimerInfo(); - void update(); - - F64HertzImplicit mClockFrequency; - F64SecondsImplicit mClockFrequencyInv; - F64MicrosecondsImplicit mClocksToMicroseconds; - U64 mTotalTimeClockCount; - U64 mLastTotalTimeClockCount; -}; - -TimerInfo& get_timer_info(); - -LL_COMMON_API U64 get_clock_count(); - -// Sleep for milliseconds -LL_COMMON_API void ms_sleep(U32 ms); -LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); - -// Returns the correct UTC time in seconds, like time(NULL). -// Useful on the viewer, which may have its local clock set wrong. -LL_COMMON_API time_t time_corrected(); - -static inline time_t time_min() -{ - if (sizeof(time_t) == 4) - { - return (time_t) INT_MIN; - } else { -#ifdef LLONG_MIN - return (time_t) LLONG_MIN; -#else - return (time_t) LONG_MIN; -#endif - } -} - -static inline time_t time_max() -{ - if (sizeof(time_t) == 4) - { - return (time_t) INT_MAX; - } else { -#ifdef LLONG_MAX - return (time_t) LLONG_MAX; -#else - return (time_t) LONG_MAX; -#endif - } -} - -// Correction factor used by time_corrected() above. -extern LL_COMMON_API S32 gUTCOffset; - -// Is the current computer (in its current time zone) -// observing daylight savings time? -LL_COMMON_API bool is_daylight_savings(); - -// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time -// Usage: -// S32 utc_time; -// utc_time = time_corrected(); -// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time); - -LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring); -LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring); - -U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds - -#endif +/** + * @file lltimer.h + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TIMER_H +#define LL_TIMER_H + +#if LL_LINUX || LL_DARWIN +#include +#endif +#include + +#include "stdtypes.h" + +#include +#include +// units conversions +#include "llunits.h" +#ifndef USEC_PER_SEC + const U32 USEC_PER_SEC = 1000000; +#endif +const U32 SEC_PER_MIN = 60; +const U32 MIN_PER_HOUR = 60; +const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN; +const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; +const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; +const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; + +class LL_COMMON_API LLTimer +{ +public: + static LLTimer *sTimer; // global timer + +protected: + U64 mLastClockCount; + U64 mExpirationTicks; + bool mStarted; + +public: + LLTimer(); + ~LLTimer(); + + static void initClass(); + static void cleanupClass(); + + // Return a high precision number of seconds since the start of + // this application instance. + static F64SecondsImplicit getElapsedSeconds() + { + if (sTimer) + { + return sTimer->getElapsedTimeF64(); + } + else + { + return 0; + } + } + + // Return a high precision usec since epoch + static U64MicrosecondsImplicit getTotalTime(); + + // Return a high precision seconds since epoch + static F64SecondsImplicit getTotalSeconds(); + + + // MANIPULATORS + void start() { reset(); mStarted = true; } + void stop() { mStarted = false; } + void reset(); // Resets the timer + void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time + void setTimerExpirySec(F32SecondsImplicit expiration); + bool checkExpirationAndReset(F32 expiration); + bool hasExpired() const; + F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset + F64SecondsImplicit getElapsedTimeAndResetF64(); + + F32SecondsImplicit getRemainingTimeF32() const; + + static bool knownBadTimer(); + + // ACCESSORS + F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds + F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds + + bool getStarted() const { return mStarted; } + + + static U64 getCurrentClockCount(); // Returns the raw clockticks +}; + +// +// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. +// +struct TimerInfo +{ + TimerInfo(); + void update(); + + F64HertzImplicit mClockFrequency; + F64SecondsImplicit mClockFrequencyInv; + F64MicrosecondsImplicit mClocksToMicroseconds; + U64 mTotalTimeClockCount; + U64 mLastTotalTimeClockCount; +}; + +TimerInfo& get_timer_info(); + +LL_COMMON_API U64 get_clock_count(); + +// Sleep for milliseconds +LL_COMMON_API void ms_sleep(U32 ms); +LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); + +// Returns the correct UTC time in seconds, like time(NULL). +// Useful on the viewer, which may have its local clock set wrong. +LL_COMMON_API time_t time_corrected(); + +static inline time_t time_min() +{ + if (sizeof(time_t) == 4) + { + return (time_t) INT_MIN; + } else { +#ifdef LLONG_MIN + return (time_t) LLONG_MIN; +#else + return (time_t) LONG_MIN; +#endif + } +} + +static inline time_t time_max() +{ + if (sizeof(time_t) == 4) + { + return (time_t) INT_MAX; + } else { +#ifdef LLONG_MAX + return (time_t) LLONG_MAX; +#else + return (time_t) LONG_MAX; +#endif + } +} + +// Correction factor used by time_corrected() above. +extern LL_COMMON_API S32 gUTCOffset; + +// Is the current computer (in its current time zone) +// observing daylight savings time? +LL_COMMON_API bool is_daylight_savings(); + +// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time +// Usage: +// S32 utc_time; +// utc_time = time_corrected(); +// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time); + +LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring); +LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring); + +U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds + +#endif diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index b4664318cc..df82276cd2 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -1,758 +1,758 @@ -/** - * @file lluri.cpp - * @author Phoenix - * @date 2006-02-08 - * @brief Implementation of the LLURI class. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llapp.h" -#include "lluri.h" -#include "llsd.h" -#include - -#include "lluuid.h" - -// system includes -#include -#include -#include - -// static -void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) -{ - ostr << "%" - - << std::uppercase - << std::hex - << std::setw(2) - << std::setfill('0') - - // VWR-4010 Cannot cast to U32 because sign-extension on - // chars > 128 will result in FFFFFFC3 instead of F3. - << static_cast(static_cast(val)) - - // reset stream state - << std::nouppercase - << std::dec - << std::setfill(' '); -} - -// static -std::string LLURI::escape( - const std::string& str, - const std::string& allowed, - bool is_allowed_sorted) -{ - // *NOTE: This size determination feels like a good value to - // me. If someone wante to come up with a more precise heuristic - // with some data to back up the assertion that 'sort is good' - // then feel free to change this test a bit. - if(!is_allowed_sorted && (str.size() > 2 * allowed.size())) - { - // if it's already sorted, or if the url is quite long, we - // want to optimize this process. - std::string sorted_allowed(allowed); - std::sort(sorted_allowed.begin(), sorted_allowed.end()); - return escape(str, sorted_allowed, true); - } - - std::ostringstream ostr; - std::string::const_iterator it = str.begin(); - std::string::const_iterator end = str.end(); - std::string::value_type c; - if(is_allowed_sorted) - { - std::string::const_iterator allowed_begin(allowed.begin()); - std::string::const_iterator allowed_end(allowed.end()); - for(; it != end; ++it) - { - c = *it; - if(std::binary_search(allowed_begin, allowed_end, c)) - { - ostr << c; - } - else - { - encodeCharacter(ostr, c); - } - } - } - else - { - for(; it != end; ++it) - { - c = *it; - if(allowed.find(c) == std::string::npos) - { - encodeCharacter(ostr, c); - } - else - { - ostr << c; - } - } - } - return ostr.str(); -} - -// static -std::string LLURI::unescape(const std::string& str) -{ - std::ostringstream ostr; - std::string::const_iterator it = str.begin(); - std::string::const_iterator end = str.end(); - for(; it != end; ++it) - { - if((*it) == '%') - { - ++it; - if(it == end) break; - - if(is_char_hex(*it)) - { - U8 c = hex_as_nybble(*it++); - - c = c << 4; - if (it == end) break; - - if(is_char_hex(*it)) - { - c |= hex_as_nybble(*it); - ostr.put((char)c); - } - else - { - ostr.put((char)c); - ostr.put(*it); - } - } - else - { - ostr.put('%'); - ostr.put(*it); - } - } - else - { - ostr.put(*it); - } - } - return ostr.str(); -} - -namespace -{ - const std::string unreserved() - { - static const std::string s = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - return s; - } - const std::string path() - { - static const std::string s = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "$-_.+" - "!*'()," - "{}|\\^~[]`" - "<>#%" - ";/?:@&="; - return s; - } - const std::string sub_delims() - { - static const std::string s = "!$&'()*+,;="; - return s; - } - - std::string escapeHostAndPort(const std::string& s) - { return LLURI::escape(s, unreserved() + sub_delims() +":"); } - std::string escapePathComponent(const std::string& s) - { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); } - std::string escapeQueryVariable(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@" - std::string escapeQueryValue(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@" - std::string escapeUriQuery(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); } - std::string escapeUriData(const std::string& s) - { return LLURI::escape(s, unreserved() + "%"); } - std::string escapeUriPath(const std::string& s) - { return LLURI::escape(s, path()); } -} - -//static -std::string LLURI::escape(const std::string& str) -{ - static std::string default_allowed = unreserved(); - static bool initialized = false; - if(!initialized) - { - std::sort(default_allowed.begin(), default_allowed.end()); - initialized = true; - } - return escape(str, default_allowed, true); -} - -//static -std::string LLURI::escapePathAndData(const std::string &str) -{ - std::string result; - - const std::string data_marker = "data:"; - if (str.compare(0, data_marker.length(), data_marker) == 0) - { - // This is not url, but data, data part needs to be properly escaped - // data part is separated by ',' from header. Minimal data uri is "data:," - // See "data URI scheme" - size_t separator = str.find(','); - if (separator != std::string::npos) - { - size_t header_size = separator + 1; - std::string header = str.substr(0, header_size); - // base64 is url-safe - if (header.find("base64") != std::string::npos) - { - // assume url-safe data - result = str; - } - else - { - std::string data = str.substr(header_size, str.length() - header_size); - - // Notes: File can be partially pre-escaped, that's why escaping ignores '%' - // It somewhat limits user from displaying strings like "%20" in text - // but that's how viewer worked for a while and user can double-escape it - - - // Header doesn't need escaping - result = header + escapeUriData(data); - } - } - } - else - { - // try processing it as path with query separator - // The query component is indicated by the first question - // mark("?") character and terminated by a number sign("#") - size_t delim_pos = str.find('?'); - if (delim_pos == std::string::npos) - { - // alternate separator - delim_pos = str.find(';'); - } - - if (delim_pos != std::string::npos) - { - size_t path_size = delim_pos + 1; - std::string query; - std::string fragment; - - size_t fragment_pos = str.find('#'); - if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos)) - { - query = str.substr(path_size, fragment_pos - path_size); - fragment = str.substr(fragment_pos); - } - else - { - query = str.substr(path_size); - } - - std::string path = str.substr(0, path_size); - - result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment); - } - } - - if (result.empty()) - { - // Not a known scheme or no data part, try just escaping as Uri path - result = escapeUriPath(str); - } - return result; -} - -LLURI::LLURI() -{ -} - -LLURI::LLURI(const std::string& escaped_str) -{ - std::string::size_type delim_pos; - delim_pos = escaped_str.find(':'); - std::string temp; - if (delim_pos == std::string::npos) - { - mScheme = ""; - mEscapedOpaque = escaped_str; - } - else - { - mScheme = escaped_str.substr(0, delim_pos); - mEscapedOpaque = escaped_str.substr(delim_pos+1); - } - - parseAuthorityAndPathUsingOpaque(); - - delim_pos = mEscapedPath.find('?'); - if (delim_pos != std::string::npos) - { - mEscapedQuery = mEscapedPath.substr(delim_pos+1); - mEscapedPath = mEscapedPath.substr(0,delim_pos); - } -} - -static bool isDefault(const std::string& scheme, U16 port) -{ - if (scheme == "http") - return port == 80; - if (scheme == "https") - return port == 443; - if (scheme == "ftp") - return port == 21; - - return false; -} - -void LLURI::parseAuthorityAndPathUsingOpaque() -{ - if (mScheme == "http" || mScheme == "https" || - mScheme == "ftp" || mScheme == "secondlife" || - mScheme == "x-grid-location-info") - { - if (mEscapedOpaque.substr(0,2) != "//") - { - return; - } - - std::string::size_type delim_pos, delim_pos2; - delim_pos = mEscapedOpaque.find('/', 2); - delim_pos2 = mEscapedOpaque.find('?', 2); - // no path, no query - if (delim_pos == std::string::npos && - delim_pos2 == std::string::npos) - { - mEscapedAuthority = mEscapedOpaque.substr(2); - mEscapedPath = ""; - } - // path exist, no query - else if (delim_pos2 == std::string::npos) - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); - mEscapedPath = mEscapedOpaque.substr(delim_pos); - } - // no path, only query - else if (delim_pos == std::string::npos || - delim_pos2 < delim_pos) - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); - // query part will be broken out later - mEscapedPath = mEscapedOpaque.substr(delim_pos2); - } - // path and query - else - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); - // query part will be broken out later - mEscapedPath = mEscapedOpaque.substr(delim_pos); - } - } - else if (mScheme == "about") - { - mEscapedPath = mEscapedOpaque; - } -} - -LLURI::LLURI(const std::string& scheme, - const std::string& userName, - const std::string& password, - const std::string& hostName, - U16 port, - const std::string& escapedPath, - const std::string& escapedQuery) - : mScheme(scheme), - mEscapedPath(escapedPath), - mEscapedQuery(escapedQuery) -{ - std::ostringstream auth; - std::ostringstream opaque; - - opaque << "//"; - - if (!userName.empty()) - { - auth << escape(userName); - if (!password.empty()) - { - auth << ':' << escape(password); - } - auth << '@'; - } - auth << hostName; - if (!isDefault(scheme, port)) - { - auth << ':' << port; - } - mEscapedAuthority = auth.str(); - - opaque << mEscapedAuthority << escapedPath << escapedQuery; - - mEscapedOpaque = opaque.str(); -} - -LLURI::~LLURI() -{ -} - -// static -LLURI LLURI::buildHTTP(const std::string& prefix, - const LLSD& path) -{ - LLURI result; - - // TODO: deal with '/' '?' '#' in host_port - if (prefix.find("://") != prefix.npos) - { - // it is a prefix - result = LLURI(prefix); - } - else - { - // it is just a host and optional port - result.mScheme = "http"; - result.mEscapedAuthority = escapeHostAndPort(prefix); - } - - if (path.isArray()) - { - // break out and escape each path component - for (LLSD::array_const_iterator it = path.beginArray(); - it != path.endArray(); - ++it) - { - LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL; - result.mEscapedPath += "/" + escapePathComponent(it->asString()); - } - } - else if (path.isString()) - { - std::string pathstr(path); - // Trailing slash is significant in HTTP land. If caller specified, - // make a point of preserving. - std::string last_slash; - std::string::size_type len(pathstr.length()); - if (len && pathstr[len-1] == '/') - { - last_slash = "/"; - } - - // Escape every individual path component, recombining with slashes. - for (boost::split_iterator - ti(pathstr, boost::first_finder("/")), tend; - ti != tend; ++ti) - { - // Eliminate a leading slash or duplicate slashes anywhere. (Extra - // slashes show up here as empty components.) This test also - // eliminates a trailing slash, hence last_slash above. - if (! ti->empty()) - { - result.mEscapedPath - += "/" + escapePathComponent(std::string(ti->begin(), ti->end())); - } - } - - // Reinstate trailing slash, if any. - result.mEscapedPath += last_slash; - } - else if(path.isUndefined()) - { - // do nothing - } - else - { - LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" - << path.type() << LL_ENDL; - } - result.mEscapedOpaque = "//" + result.mEscapedAuthority + - result.mEscapedPath; - return result; -} - -// static -LLURI LLURI::buildHTTP(const std::string& prefix, - const LLSD& path, - const LLSD& query) -{ - LLURI uri = buildHTTP(prefix, path); - // break out and escape each query component - uri.mEscapedQuery = mapToQueryString(query); - uri.mEscapedOpaque += uri.mEscapedQuery ; - uri.mEscapedQuery.erase(0,1); // trim the leading '?' - return uri; -} - -// static -LLURI LLURI::buildHTTP(const std::string& host, - const U32& port, - const LLSD& path) -{ - return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path); -} - -// static -LLURI LLURI::buildHTTP(const std::string& host, - const U32& port, - const LLSD& path, - const LLSD& query) -{ - return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query); -} - -std::string LLURI::asString() const -{ - if (mScheme.empty()) - { - return mEscapedOpaque; - } - else - { - return mScheme + ":" + mEscapedOpaque; - } -} - -std::string LLURI::scheme() const -{ - return mScheme; -} - -std::string LLURI::opaque() const -{ - return unescape(mEscapedOpaque); -} - -std::string LLURI::authority() const -{ - return unescape(mEscapedAuthority); -} - - -namespace { - void findAuthorityParts(const std::string& authority, - std::string& user, - std::string& host, - std::string& port) - { - std::string::size_type start_pos = authority.find('@'); - if (start_pos == std::string::npos) - { - user = ""; - start_pos = 0; - } - else - { - user = authority.substr(0, start_pos); - start_pos += 1; - } - - std::string::size_type end_pos = authority.find(':', start_pos); - if (end_pos == std::string::npos) - { - host = authority.substr(start_pos); - port = ""; - } - else - { - host = authority.substr(start_pos, end_pos - start_pos); - port = authority.substr(end_pos + 1); - } - } -} - -std::string LLURI::hostName() const -{ - std::string user, host, port; - findAuthorityParts(mEscapedAuthority, user, host, port); - return unescape(host); -} - -std::string LLURI::userName() const -{ - std::string user, userPass, host, port; - findAuthorityParts(mEscapedAuthority, userPass, host, port); - std::string::size_type pos = userPass.find(':'); - if (pos != std::string::npos) - { - user = userPass.substr(0, pos); - } - return unescape(user); -} - -std::string LLURI::password() const -{ - std::string pass, userPass, host, port; - findAuthorityParts(mEscapedAuthority, userPass, host, port); - std::string::size_type pos = userPass.find(':'); - if (pos != std::string::npos) - { - pass = userPass.substr(pos + 1); - } - return unescape(pass); -} - -bool LLURI::defaultPort() const -{ - return isDefault(mScheme, hostPort()); -} - -U16 LLURI::hostPort() const -{ - std::string user, host, port; - findAuthorityParts(mEscapedAuthority, user, host, port); - if (port.empty()) - { - if (mScheme == "http") - return 80; - if (mScheme == "https") - return 443; - if (mScheme == "ftp") - return 21; - return 0; - } - return atoi(port.c_str()); -} - -std::string LLURI::path() const -{ - return unescape(mEscapedPath); -} - -LLSD LLURI::pathArray() const -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("/", "", boost::drop_empty_tokens); - tokenizer tokens(mEscapedPath, sep); - tokenizer::iterator it = tokens.begin(); - tokenizer::iterator end = tokens.end(); - - LLSD params; - for (const std::string& str : tokens) - { - params.append(str); - } - return params; -} - -std::string LLURI::query() const -{ - return unescape(mEscapedQuery); -} - -LLSD LLURI::queryMap() const -{ - return queryMap(mEscapedQuery); -} - -// static -LLSD LLURI::queryMap(std::string escaped_query_string) -{ - LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL; - - LLSD result = LLSD::emptyArray(); - while(!escaped_query_string.empty()) - { - // get tuple first - std::string tuple; - std::string::size_type tuple_begin = escaped_query_string.find('&'); - if (tuple_begin != std::string::npos) - { - tuple = escaped_query_string.substr(0, tuple_begin); - escaped_query_string = escaped_query_string.substr(tuple_begin+1); - } - else - { - tuple = escaped_query_string; - escaped_query_string = ""; - } - if (tuple.empty()) continue; - - // parse tuple - std::string::size_type key_end = tuple.find('='); - if (key_end != std::string::npos) - { - std::string key = unescape(tuple.substr(0,key_end)); - std::string value = unescape(tuple.substr(key_end+1)); - LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL; - result[key] = value; - } - else - { - LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL; - result[unescape(tuple)] = true; - } - } - return result; -} - -std::string LLURI::mapToQueryString(const LLSD& queryMap) -{ - std::string query_string; - if (queryMap.isMap()) - { - bool first_element = true; - LLSD::map_const_iterator iter = queryMap.beginMap(); - LLSD::map_const_iterator end = queryMap.endMap(); - std::ostringstream ostr; - for (; iter != end; ++iter) - { - if(first_element) - { - ostr << "?"; - first_element = false; - } - else - { - ostr << "&"; - } - ostr << escapeQueryVariable(iter->first); - if(iter->second.isDefined()) - { - ostr << "=" << escapeQueryValue(iter->second.asString()); - } - } - query_string = ostr.str(); - } - return query_string; -} - -bool operator!=(const LLURI& first, const LLURI& second) -{ - return (first.asString() != second.asString()); -} +/** + * @file lluri.cpp + * @author Phoenix + * @date 2006-02-08 + * @brief Implementation of the LLURI class. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llapp.h" +#include "lluri.h" +#include "llsd.h" +#include + +#include "lluuid.h" + +// system includes +#include +#include +#include + +// static +void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) +{ + ostr << "%" + + << std::uppercase + << std::hex + << std::setw(2) + << std::setfill('0') + + // VWR-4010 Cannot cast to U32 because sign-extension on + // chars > 128 will result in FFFFFFC3 instead of F3. + << static_cast(static_cast(val)) + + // reset stream state + << std::nouppercase + << std::dec + << std::setfill(' '); +} + +// static +std::string LLURI::escape( + const std::string& str, + const std::string& allowed, + bool is_allowed_sorted) +{ + // *NOTE: This size determination feels like a good value to + // me. If someone wante to come up with a more precise heuristic + // with some data to back up the assertion that 'sort is good' + // then feel free to change this test a bit. + if(!is_allowed_sorted && (str.size() > 2 * allowed.size())) + { + // if it's already sorted, or if the url is quite long, we + // want to optimize this process. + std::string sorted_allowed(allowed); + std::sort(sorted_allowed.begin(), sorted_allowed.end()); + return escape(str, sorted_allowed, true); + } + + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + std::string::value_type c; + if(is_allowed_sorted) + { + std::string::const_iterator allowed_begin(allowed.begin()); + std::string::const_iterator allowed_end(allowed.end()); + for(; it != end; ++it) + { + c = *it; + if(std::binary_search(allowed_begin, allowed_end, c)) + { + ostr << c; + } + else + { + encodeCharacter(ostr, c); + } + } + } + else + { + for(; it != end; ++it) + { + c = *it; + if(allowed.find(c) == std::string::npos) + { + encodeCharacter(ostr, c); + } + else + { + ostr << c; + } + } + } + return ostr.str(); +} + +// static +std::string LLURI::unescape(const std::string& str) +{ + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + for(; it != end; ++it) + { + if((*it) == '%') + { + ++it; + if(it == end) break; + + if(is_char_hex(*it)) + { + U8 c = hex_as_nybble(*it++); + + c = c << 4; + if (it == end) break; + + if(is_char_hex(*it)) + { + c |= hex_as_nybble(*it); + ostr.put((char)c); + } + else + { + ostr.put((char)c); + ostr.put(*it); + } + } + else + { + ostr.put('%'); + ostr.put(*it); + } + } + else + { + ostr.put(*it); + } + } + return ostr.str(); +} + +namespace +{ + const std::string unreserved() + { + static const std::string s = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + return s; + } + const std::string path() + { + static const std::string s = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "$-_.+" + "!*'()," + "{}|\\^~[]`" + "<>#%" + ";/?:@&="; + return s; + } + const std::string sub_delims() + { + static const std::string s = "!$&'()*+,;="; + return s; + } + + std::string escapeHostAndPort(const std::string& s) + { return LLURI::escape(s, unreserved() + sub_delims() +":"); } + std::string escapePathComponent(const std::string& s) + { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); } + std::string escapeQueryVariable(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@" + std::string escapeQueryValue(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@" + std::string escapeUriQuery(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); } + std::string escapeUriData(const std::string& s) + { return LLURI::escape(s, unreserved() + "%"); } + std::string escapeUriPath(const std::string& s) + { return LLURI::escape(s, path()); } +} + +//static +std::string LLURI::escape(const std::string& str) +{ + static std::string default_allowed = unreserved(); + static bool initialized = false; + if(!initialized) + { + std::sort(default_allowed.begin(), default_allowed.end()); + initialized = true; + } + return escape(str, default_allowed, true); +} + +//static +std::string LLURI::escapePathAndData(const std::string &str) +{ + std::string result; + + const std::string data_marker = "data:"; + if (str.compare(0, data_marker.length(), data_marker) == 0) + { + // This is not url, but data, data part needs to be properly escaped + // data part is separated by ',' from header. Minimal data uri is "data:," + // See "data URI scheme" + size_t separator = str.find(','); + if (separator != std::string::npos) + { + size_t header_size = separator + 1; + std::string header = str.substr(0, header_size); + // base64 is url-safe + if (header.find("base64") != std::string::npos) + { + // assume url-safe data + result = str; + } + else + { + std::string data = str.substr(header_size, str.length() - header_size); + + // Notes: File can be partially pre-escaped, that's why escaping ignores '%' + // It somewhat limits user from displaying strings like "%20" in text + // but that's how viewer worked for a while and user can double-escape it + + + // Header doesn't need escaping + result = header + escapeUriData(data); + } + } + } + else + { + // try processing it as path with query separator + // The query component is indicated by the first question + // mark("?") character and terminated by a number sign("#") + size_t delim_pos = str.find('?'); + if (delim_pos == std::string::npos) + { + // alternate separator + delim_pos = str.find(';'); + } + + if (delim_pos != std::string::npos) + { + size_t path_size = delim_pos + 1; + std::string query; + std::string fragment; + + size_t fragment_pos = str.find('#'); + if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos)) + { + query = str.substr(path_size, fragment_pos - path_size); + fragment = str.substr(fragment_pos); + } + else + { + query = str.substr(path_size); + } + + std::string path = str.substr(0, path_size); + + result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment); + } + } + + if (result.empty()) + { + // Not a known scheme or no data part, try just escaping as Uri path + result = escapeUriPath(str); + } + return result; +} + +LLURI::LLURI() +{ +} + +LLURI::LLURI(const std::string& escaped_str) +{ + std::string::size_type delim_pos; + delim_pos = escaped_str.find(':'); + std::string temp; + if (delim_pos == std::string::npos) + { + mScheme = ""; + mEscapedOpaque = escaped_str; + } + else + { + mScheme = escaped_str.substr(0, delim_pos); + mEscapedOpaque = escaped_str.substr(delim_pos+1); + } + + parseAuthorityAndPathUsingOpaque(); + + delim_pos = mEscapedPath.find('?'); + if (delim_pos != std::string::npos) + { + mEscapedQuery = mEscapedPath.substr(delim_pos+1); + mEscapedPath = mEscapedPath.substr(0,delim_pos); + } +} + +static bool isDefault(const std::string& scheme, U16 port) +{ + if (scheme == "http") + return port == 80; + if (scheme == "https") + return port == 443; + if (scheme == "ftp") + return port == 21; + + return false; +} + +void LLURI::parseAuthorityAndPathUsingOpaque() +{ + if (mScheme == "http" || mScheme == "https" || + mScheme == "ftp" || mScheme == "secondlife" || + mScheme == "x-grid-location-info") + { + if (mEscapedOpaque.substr(0,2) != "//") + { + return; + } + + std::string::size_type delim_pos, delim_pos2; + delim_pos = mEscapedOpaque.find('/', 2); + delim_pos2 = mEscapedOpaque.find('?', 2); + // no path, no query + if (delim_pos == std::string::npos && + delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2); + mEscapedPath = ""; + } + // path exist, no query + else if (delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + // no path, only query + else if (delim_pos == std::string::npos || + delim_pos2 < delim_pos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos2); + } + // path and query + else + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + } + else if (mScheme == "about") + { + mEscapedPath = mEscapedOpaque; + } +} + +LLURI::LLURI(const std::string& scheme, + const std::string& userName, + const std::string& password, + const std::string& hostName, + U16 port, + const std::string& escapedPath, + const std::string& escapedQuery) + : mScheme(scheme), + mEscapedPath(escapedPath), + mEscapedQuery(escapedQuery) +{ + std::ostringstream auth; + std::ostringstream opaque; + + opaque << "//"; + + if (!userName.empty()) + { + auth << escape(userName); + if (!password.empty()) + { + auth << ':' << escape(password); + } + auth << '@'; + } + auth << hostName; + if (!isDefault(scheme, port)) + { + auth << ':' << port; + } + mEscapedAuthority = auth.str(); + + opaque << mEscapedAuthority << escapedPath << escapedQuery; + + mEscapedOpaque = opaque.str(); +} + +LLURI::~LLURI() +{ +} + +// static +LLURI LLURI::buildHTTP(const std::string& prefix, + const LLSD& path) +{ + LLURI result; + + // TODO: deal with '/' '?' '#' in host_port + if (prefix.find("://") != prefix.npos) + { + // it is a prefix + result = LLURI(prefix); + } + else + { + // it is just a host and optional port + result.mScheme = "http"; + result.mEscapedAuthority = escapeHostAndPort(prefix); + } + + if (path.isArray()) + { + // break out and escape each path component + for (LLSD::array_const_iterator it = path.beginArray(); + it != path.endArray(); + ++it) + { + LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL; + result.mEscapedPath += "/" + escapePathComponent(it->asString()); + } + } + else if (path.isString()) + { + std::string pathstr(path); + // Trailing slash is significant in HTTP land. If caller specified, + // make a point of preserving. + std::string last_slash; + std::string::size_type len(pathstr.length()); + if (len && pathstr[len-1] == '/') + { + last_slash = "/"; + } + + // Escape every individual path component, recombining with slashes. + for (boost::split_iterator + ti(pathstr, boost::first_finder("/")), tend; + ti != tend; ++ti) + { + // Eliminate a leading slash or duplicate slashes anywhere. (Extra + // slashes show up here as empty components.) This test also + // eliminates a trailing slash, hence last_slash above. + if (! ti->empty()) + { + result.mEscapedPath + += "/" + escapePathComponent(std::string(ti->begin(), ti->end())); + } + } + + // Reinstate trailing slash, if any. + result.mEscapedPath += last_slash; + } + else if(path.isUndefined()) + { + // do nothing + } + else + { + LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" + << path.type() << LL_ENDL; + } + result.mEscapedOpaque = "//" + result.mEscapedAuthority + + result.mEscapedPath; + return result; +} + +// static +LLURI LLURI::buildHTTP(const std::string& prefix, + const LLSD& path, + const LLSD& query) +{ + LLURI uri = buildHTTP(prefix, path); + // break out and escape each query component + uri.mEscapedQuery = mapToQueryString(query); + uri.mEscapedOpaque += uri.mEscapedQuery ; + uri.mEscapedQuery.erase(0,1); // trim the leading '?' + return uri; +} + +// static +LLURI LLURI::buildHTTP(const std::string& host, + const U32& port, + const LLSD& path) +{ + return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path); +} + +// static +LLURI LLURI::buildHTTP(const std::string& host, + const U32& port, + const LLSD& path, + const LLSD& query) +{ + return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query); +} + +std::string LLURI::asString() const +{ + if (mScheme.empty()) + { + return mEscapedOpaque; + } + else + { + return mScheme + ":" + mEscapedOpaque; + } +} + +std::string LLURI::scheme() const +{ + return mScheme; +} + +std::string LLURI::opaque() const +{ + return unescape(mEscapedOpaque); +} + +std::string LLURI::authority() const +{ + return unescape(mEscapedAuthority); +} + + +namespace { + void findAuthorityParts(const std::string& authority, + std::string& user, + std::string& host, + std::string& port) + { + std::string::size_type start_pos = authority.find('@'); + if (start_pos == std::string::npos) + { + user = ""; + start_pos = 0; + } + else + { + user = authority.substr(0, start_pos); + start_pos += 1; + } + + std::string::size_type end_pos = authority.find(':', start_pos); + if (end_pos == std::string::npos) + { + host = authority.substr(start_pos); + port = ""; + } + else + { + host = authority.substr(start_pos, end_pos - start_pos); + port = authority.substr(end_pos + 1); + } + } +} + +std::string LLURI::hostName() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + return unescape(host); +} + +std::string LLURI::userName() const +{ + std::string user, userPass, host, port; + findAuthorityParts(mEscapedAuthority, userPass, host, port); + std::string::size_type pos = userPass.find(':'); + if (pos != std::string::npos) + { + user = userPass.substr(0, pos); + } + return unescape(user); +} + +std::string LLURI::password() const +{ + std::string pass, userPass, host, port; + findAuthorityParts(mEscapedAuthority, userPass, host, port); + std::string::size_type pos = userPass.find(':'); + if (pos != std::string::npos) + { + pass = userPass.substr(pos + 1); + } + return unescape(pass); +} + +bool LLURI::defaultPort() const +{ + return isDefault(mScheme, hostPort()); +} + +U16 LLURI::hostPort() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + if (port.empty()) + { + if (mScheme == "http") + return 80; + if (mScheme == "https") + return 443; + if (mScheme == "ftp") + return 21; + return 0; + } + return atoi(port.c_str()); +} + +std::string LLURI::path() const +{ + return unescape(mEscapedPath); +} + +LLSD LLURI::pathArray() const +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("/", "", boost::drop_empty_tokens); + tokenizer tokens(mEscapedPath, sep); + tokenizer::iterator it = tokens.begin(); + tokenizer::iterator end = tokens.end(); + + LLSD params; + for (const std::string& str : tokens) + { + params.append(str); + } + return params; +} + +std::string LLURI::query() const +{ + return unescape(mEscapedQuery); +} + +LLSD LLURI::queryMap() const +{ + return queryMap(mEscapedQuery); +} + +// static +LLSD LLURI::queryMap(std::string escaped_query_string) +{ + LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL; + + LLSD result = LLSD::emptyArray(); + while(!escaped_query_string.empty()) + { + // get tuple first + std::string tuple; + std::string::size_type tuple_begin = escaped_query_string.find('&'); + if (tuple_begin != std::string::npos) + { + tuple = escaped_query_string.substr(0, tuple_begin); + escaped_query_string = escaped_query_string.substr(tuple_begin+1); + } + else + { + tuple = escaped_query_string; + escaped_query_string = ""; + } + if (tuple.empty()) continue; + + // parse tuple + std::string::size_type key_end = tuple.find('='); + if (key_end != std::string::npos) + { + std::string key = unescape(tuple.substr(0,key_end)); + std::string value = unescape(tuple.substr(key_end+1)); + LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL; + result[key] = value; + } + else + { + LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL; + result[unescape(tuple)] = true; + } + } + return result; +} + +std::string LLURI::mapToQueryString(const LLSD& queryMap) +{ + std::string query_string; + if (queryMap.isMap()) + { + bool first_element = true; + LLSD::map_const_iterator iter = queryMap.beginMap(); + LLSD::map_const_iterator end = queryMap.endMap(); + std::ostringstream ostr; + for (; iter != end; ++iter) + { + if(first_element) + { + ostr << "?"; + first_element = false; + } + else + { + ostr << "&"; + } + ostr << escapeQueryVariable(iter->first); + if(iter->second.isDefined()) + { + ostr << "=" << escapeQueryValue(iter->second.asString()); + } + } + query_string = ostr.str(); + } + return query_string; +} + +bool operator!=(const LLURI& first, const LLURI& second) +{ + return (first.asString() != second.asString()); +} diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index cc59c6b370..37ee0a0ac7 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -1,193 +1,193 @@ -/** - * @file lluri.h - * @author Phoenix - * @date 2006-02-05 - * @brief Declaration of the URI class. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLURI_H -#define LL_LLURI_H - -#include - -class LLSD; -class LLUUID; -class LLApp; - -/** - * - * LLURI instances are immutable - * See: http://www.ietf.org/rfc/rfc3986.txt - * - */ -class LL_COMMON_API LLURI -{ -public: - LLURI(); - LLURI(const std::string& escaped_str); - LLURI(const std::string& scheme, - const std::string& userName, - const std::string& password, - const std::string& hostName, - U16 hostPort, - const std::string& escapedPath, - const std::string& escapedQuery); - - // construct from escaped string, as would be transmitted on the net - - ~LLURI(); - - static LLURI buildHTTP( - const std::string& prefix, - const LLSD& path); - - static LLURI buildHTTP( - const std::string& prefix, - const LLSD& path, - const LLSD& query); - ///< prefix is either a full URL prefix of the form - /// "http://example.com:8080", or it can be simply a host and - /// optional port like "example.com" or "example.com:8080", in - /// these cases, the "http://" will be added - - static LLURI buildHTTP( - const std::string& host, - const U32& port, - const LLSD& path); - static LLURI buildHTTP( - const std::string& host, - const U32& port, - const LLSD& path, - const LLSD& query); - - std::string asString() const; - ///< the whole URI, escaped as needed - - /** @name Parts of a URI */ - //@{ - // These functions return parts of the decoded URI. The returned - // strings are un-escaped as needed - - // for all schemes - std::string scheme() const; ///< ex.: "http", note lack of colon - std::string opaque() const; ///< everything after the colon - - // for schemes that follow path like syntax (http, https, ftp) - std::string authority() const; // ex.: "host.com:80" - std::string hostName() const; // ex.: "host.com" - std::string userName() const; - std::string password() const; - U16 hostPort() const; // ex.: 80, will include implicit port - bool defaultPort() const; // true if port is default for scheme - const std::string& escapedPath() const { return mEscapedPath; } - std::string path() const; // ex.: "/abc/def", includes leading slash - LLSD pathArray() const; // above decoded into an array of strings - std::string query() const; // ex.: "x=34", section after "?" - const std::string& escapedQuery() const { return mEscapedQuery; } - LLSD queryMap() const; // above decoded into a map - static LLSD queryMap(std::string escaped_query_string); - - /** - * @brief given a name value map, return a serialized query string. - * - - * @param query_map a map of name value. every value must be - * representable as a string. - * @return Returns an url query string of '?n1=v1&n2=v2&...' - */ - static std::string mapToQueryString(const LLSD& query_map); - - /** @name Escaping Utilities */ - //@{ - /** - * @brief 'Escape' symbol into stream - * - * @param ostr Output stream. - * @param val Symbol to encode. - */ - static void encodeCharacter(std::ostream& ostr, std::string::value_type val); - - /** - * @brief Escape the string passed except for unreserved - * - * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz - * 0123456789 - * -._~ - * - * @see http://www.ietf.org/rfc/rfc1738.txt - * - * @param str The raw URI to escape. - * @return Returns the rfc 1738 escaped uri or an empty string. - */ - static std::string escape(const std::string& str); - - /** - * @brief Escape a string with a specified set of allowed characters. - * - * Escape a string by urlencoding all the characters that aren't - * in the allowed string. - * @param str The raw URI to escape. - * @param allowed Character array of allowed characters - * @param is_allowed_sorted Optimization hint if allowed array is sorted. - * @return Returns the escaped uri or an empty string. - */ - static std::string escape( - const std::string& str, - const std::string& allowed, - bool is_allowed_sorted = false); - - /** - * @brief Break string into data part and path or sheme - * and escape path (if present) and data. - * Data part is not allowed to have path related symbols - * @param str The raw URI to escape. - */ - static std::string escapePathAndData(const std::string &str); - - /** - * @brief unescape an escaped URI string. - * - * @param str The escped URI to unescape. - * @return Returns the unescaped uri or an empty string. - */ - static std::string unescape(const std::string& str); - //@} - -private: - // only "http", "https", "ftp", and "secondlife" schemes are parsed - // secondlife scheme parses authority as "" and includes it as part of - // the path. See lluri_tut.cpp - // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login" - void parseAuthorityAndPathUsingOpaque(); - std::string mScheme; - std::string mEscapedOpaque; - std::string mEscapedAuthority; - std::string mEscapedPath; - std::string mEscapedQuery; -}; - -// this operator required for tut -LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); - -#endif // LL_LLURI_H +/** + * @file lluri.h + * @author Phoenix + * @date 2006-02-05 + * @brief Declaration of the URI class. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLURI_H +#define LL_LLURI_H + +#include + +class LLSD; +class LLUUID; +class LLApp; + +/** + * + * LLURI instances are immutable + * See: http://www.ietf.org/rfc/rfc3986.txt + * + */ +class LL_COMMON_API LLURI +{ +public: + LLURI(); + LLURI(const std::string& escaped_str); + LLURI(const std::string& scheme, + const std::string& userName, + const std::string& password, + const std::string& hostName, + U16 hostPort, + const std::string& escapedPath, + const std::string& escapedQuery); + + // construct from escaped string, as would be transmitted on the net + + ~LLURI(); + + static LLURI buildHTTP( + const std::string& prefix, + const LLSD& path); + + static LLURI buildHTTP( + const std::string& prefix, + const LLSD& path, + const LLSD& query); + ///< prefix is either a full URL prefix of the form + /// "http://example.com:8080", or it can be simply a host and + /// optional port like "example.com" or "example.com:8080", in + /// these cases, the "http://" will be added + + static LLURI buildHTTP( + const std::string& host, + const U32& port, + const LLSD& path); + static LLURI buildHTTP( + const std::string& host, + const U32& port, + const LLSD& path, + const LLSD& query); + + std::string asString() const; + ///< the whole URI, escaped as needed + + /** @name Parts of a URI */ + //@{ + // These functions return parts of the decoded URI. The returned + // strings are un-escaped as needed + + // for all schemes + std::string scheme() const; ///< ex.: "http", note lack of colon + std::string opaque() const; ///< everything after the colon + + // for schemes that follow path like syntax (http, https, ftp) + std::string authority() const; // ex.: "host.com:80" + std::string hostName() const; // ex.: "host.com" + std::string userName() const; + std::string password() const; + U16 hostPort() const; // ex.: 80, will include implicit port + bool defaultPort() const; // true if port is default for scheme + const std::string& escapedPath() const { return mEscapedPath; } + std::string path() const; // ex.: "/abc/def", includes leading slash + LLSD pathArray() const; // above decoded into an array of strings + std::string query() const; // ex.: "x=34", section after "?" + const std::string& escapedQuery() const { return mEscapedQuery; } + LLSD queryMap() const; // above decoded into a map + static LLSD queryMap(std::string escaped_query_string); + + /** + * @brief given a name value map, return a serialized query string. + * + + * @param query_map a map of name value. every value must be + * representable as a string. + * @return Returns an url query string of '?n1=v1&n2=v2&...' + */ + static std::string mapToQueryString(const LLSD& query_map); + + /** @name Escaping Utilities */ + //@{ + /** + * @brief 'Escape' symbol into stream + * + * @param ostr Output stream. + * @param val Symbol to encode. + */ + static void encodeCharacter(std::ostream& ostr, std::string::value_type val); + + /** + * @brief Escape the string passed except for unreserved + * + * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz + * 0123456789 + * -._~ + * + * @see http://www.ietf.org/rfc/rfc1738.txt + * + * @param str The raw URI to escape. + * @return Returns the rfc 1738 escaped uri or an empty string. + */ + static std::string escape(const std::string& str); + + /** + * @brief Escape a string with a specified set of allowed characters. + * + * Escape a string by urlencoding all the characters that aren't + * in the allowed string. + * @param str The raw URI to escape. + * @param allowed Character array of allowed characters + * @param is_allowed_sorted Optimization hint if allowed array is sorted. + * @return Returns the escaped uri or an empty string. + */ + static std::string escape( + const std::string& str, + const std::string& allowed, + bool is_allowed_sorted = false); + + /** + * @brief Break string into data part and path or sheme + * and escape path (if present) and data. + * Data part is not allowed to have path related symbols + * @param str The raw URI to escape. + */ + static std::string escapePathAndData(const std::string &str); + + /** + * @brief unescape an escaped URI string. + * + * @param str The escped URI to unescape. + * @return Returns the unescaped uri or an empty string. + */ + static std::string unescape(const std::string& str); + //@} + +private: + // only "http", "https", "ftp", and "secondlife" schemes are parsed + // secondlife scheme parses authority as "" and includes it as part of + // the path. See lluri_tut.cpp + // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login" + void parseAuthorityAndPathUsingOpaque(); + std::string mScheme; + std::string mEscapedOpaque; + std::string mEscapedAuthority; + std::string mEscapedPath; + std::string mEscapedQuery; +}; + +// this operator required for tut +LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); + +#endif // LL_LLURI_H diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index f3821de71b..3b37365ec7 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -1,1077 +1,1077 @@ -/** - * @file lluuid.cpp - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - - // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. -#if LL_WINDOWS -#include "llwin32headers.h" -// ugh, this is ugly. We need to straighten out our linking for this library -#pragma comment(lib, "IPHLPAPI.lib") -#include -#endif - -#include "llapp.h" -#include "lldefs.h" -#include "llerror.h" - -#include "lluuid.h" -#include "llerror.h" -#include "llrand.h" -#include "llstring.h" -#include "lltimer.h" -#include "llthread.h" -#include "llmutex.h" -#include "llmd5.h" -#include "hbxxh.h" - -const LLUUID LLUUID::null; -const LLTransactionID LLTransactionID::tnull; - -// static -LLMutex* LLUUID::mMutex = NULL; - - - -/* - -NOT DONE YET!!! - -static char BASE85_TABLE[] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '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', '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' -}; - - -void encode( char * fiveChars, unsigned int word ) throw( ) -{ -for( int ix = 0; ix < 5; ++ix ) { -fiveChars[4-ix] = encodeTable[ word % 85]; -word /= 85; -} -} - -To decode: -unsigned int decode( char const * fiveChars ) throw( bad_input_data ) -{ -unsigned int ret = 0; -for( int ix = 0; ix < 5; ++ix ) { -char * s = strchr( encodeTable, fiveChars[ ix ] ); -if( s == 0 ) LLTHROW(bad_input_data()); -ret = ret * 85 + (s-encodeTable); -} -return ret; -} - -void LLUUID::toBase85(char* out) -{ - U32* me = (U32*)&(mData[0]); - for(S32 i = 0; i < 4; ++i) - { - char* o = &out[i*i]; - for(S32 j = 0; j < 5; ++j) - { - o[4-j] = BASE85_TABLE[ me[i] % 85]; - word /= 85; - } - } -} - -unsigned int decode( char const * fiveChars ) throw( bad_input_data ) -{ - unsigned int ret = 0; - for( S32 ix = 0; ix < 5; ++ix ) - { - char * s = strchr( encodeTable, fiveChars[ ix ] ); - ret = ret * 85 + (s-encodeTable); - } - return ret; -} -*/ - -#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0 -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR -/** - * @brief a global for - */ -static U64 sJankyRandomSeed(LLUUID::getRandomSeed()); - -/** - * @brief generate a random U32. - */ -U32 janky_fast_random_bytes() -{ - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)sJankyRandomSeed; -} - -/** - * @brief generate a random U32 from [0, val) - */ -U32 janky_fast_random_byes_range(U32 val) -{ - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)(sJankyRandomSeed) % val; -} - -/** - * @brief generate a random U32 from [0, val) - */ -U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) -{ - seed = U64L(1664525) * (U64)(seed)+U64L(1013904223); - return (U32)(seed) % val; -} -#endif - -// Common to all UUID implementations -void LLUUID::toString(std::string& out) const -{ - out = llformat( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - (U8)(mData[0]), - (U8)(mData[1]), - (U8)(mData[2]), - (U8)(mData[3]), - (U8)(mData[4]), - (U8)(mData[5]), - (U8)(mData[6]), - (U8)(mData[7]), - (U8)(mData[8]), - (U8)(mData[9]), - (U8)(mData[10]), - (U8)(mData[11]), - (U8)(mData[12]), - (U8)(mData[13]), - (U8)(mData[14]), - (U8)(mData[15])); -} - -// *TODO: deprecate -void LLUUID::toString(char* out) const -{ - std::string buffer; - toString(buffer); - strcpy(out, buffer.c_str()); /* Flawfinder: ignore */ -} - -void LLUUID::toCompressedString(std::string& out) const -{ - char bytes[UUID_BYTES + 1]; - memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ - bytes[UUID_BYTES] = '\0'; - out.assign(bytes, UUID_BYTES); -} - -// *TODO: deprecate -void LLUUID::toCompressedString(char* out) const -{ - memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ - out[UUID_BYTES] = '\0'; -} - -std::string LLUUID::getString() const -{ - return asString(); -} - -std::string LLUUID::asString() const -{ - std::string str; - toString(str); - return str; -} - -bool LLUUID::set(const char* in_string, bool emit) -{ - return set(ll_safe_string(in_string), emit); -} - -bool LLUUID::set(const std::string& in_string, bool emit) -{ - bool broken_format = false; - - // empty strings should make NULL uuid - if (in_string.empty()) - { - setNull(); - return true; - } - - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - // Shouldn't see any of these any more - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - if (emit) - { - LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; - } - broken_format = true; - } - else - { - // Bad UUID string. Spam as INFO, as most cases we don't care. - if (emit) - { - //don't spam the logs because a resident can't spell. - LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; - } - setNull(); - return false; - } - } - - U8 cur_pos = 0; - S32 i; - for (i = 0; i < UUID_BYTES; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i == 10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - mData[i] = 0; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if (emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return false; - } - - mData[i] = mData[i] << 4; - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if (emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return false; - } - cur_pos++; - } - - return true; -} - -bool LLUUID::validate(const std::string& in_string) -{ - bool broken_format = false; - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - broken_format = true; - } - else - { - return false; - } - } - - U8 cur_pos = 0; - for (U32 i = 0; i < 16; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i == 10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - } - else - { - return false; - } - - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - } - else - { - return false; - } - cur_pos++; - } - return true; -} - -const LLUUID& LLUUID::operator^=(const LLUUID& rhs) -{ - U32* me = (U32*)&(mData[0]); - const U32* other = (U32*)&(rhs.mData[0]); - for (S32 i = 0; i < 4; ++i) - { - me[i] = me[i] ^ other[i]; - } - return *this; -} - -LLUUID LLUUID::operator^(const LLUUID& rhs) const -{ - LLUUID id(*this); - id ^= rhs; - return id; -} - -// WARNING: this algorithm SHALL NOT be changed. It is also used by the server -// and plays a role in some assets validation (e.g. clothing items). Changing -// it would cause invalid assets. -void LLUUID::combine(const LLUUID& other, LLUUID& result) const -{ - LLMD5 md5_uuid; - md5_uuid.update((unsigned char*)mData, 16); - md5_uuid.update((unsigned char*)other.mData, 16); - md5_uuid.finalize(); - md5_uuid.raw_digest(result.mData); -} - -LLUUID LLUUID::combine(const LLUUID& other) const -{ - LLUUID combination; - combine(other, combination); - return combination; -} - -std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) -{ - std::string uuid_str; - uuid.toString(uuid_str); - s << uuid_str; - return s; -} - -std::istream& operator>>(std::istream& s, LLUUID& uuid) -{ - U32 i; - char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ - for (i = 0; i < UUID_STR_LENGTH - 1; i++) - { - s >> uuid_str[i]; - } - uuid_str[i] = '\0'; - uuid.set(std::string(uuid_str)); - return s; -} - -static void get_random_bytes(void* buf, int nbytes) -{ - int i; - char* cp = (char*)buf; - - // *NOTE: If we are not using the janky generator ll_rand() - // generates at least 3 good bytes of data since it is 0 to - // RAND_MAX. This could be made more efficient by copying all the - // bytes. - for (i = 0; i < nbytes; i++) -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - * cp++ = janky_fast_random_bytes() & 0xFF; -#else - * cp++ = ll_rand() & 0xFF; -#endif - return; -} - -#if LL_WINDOWS - -typedef struct _ASTAT_ -{ - ADAPTER_STATUS adapt; - NAME_BUFFER NameBuff[30]; -}ASTAT, * PASTAT; - -// static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - ASTAT Adapter; - NCB Ncb; - UCHAR uRetCode; - LANA_ENUM lenum; - int i; - int retval = 0; - - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBENUM; - Ncb.ncb_buffer = (UCHAR*)&lenum; - Ncb.ncb_length = sizeof(lenum); - uRetCode = Netbios(&Ncb); - - for (i = 0; i < lenum.length; i++) - { - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBRESET; - Ncb.ncb_lana_num = lenum.lana[i]; - - uRetCode = Netbios(&Ncb); - - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBASTAT; - Ncb.ncb_lana_num = lenum.lana[i]; - - strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */ - Ncb.ncb_buffer = (unsigned char*)&Adapter; - Ncb.ncb_length = sizeof(Adapter); - - uRetCode = Netbios(&Ncb); - if (uRetCode == 0) - { - memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */ - retval = 1; - } - } - return retval; -} - -#elif LL_DARWIN -// macOS version of the UUID generation code... -/* - * Get an ethernet hardware address, if we can find it... - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - // static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - int i; - unsigned char* a = NULL; - struct ifaddrs* ifap, * ifa; - int rv; - S32 result = 0; - - if ((rv = getifaddrs(&ifap)) == -1) - { - return -1; - } - if (ifap == NULL) - { - return -1; - } - - for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) - { - // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); - for (i = 0; i < ifa->ifa_addr->sa_len; i++) - { - // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); - } - // printf("\n"); - - if (ifa->ifa_addr->sa_family == AF_LINK) - { - // This is a link-level address - struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr; - - // printf("\tLink level address, type %02X\n", lla->sdl_type); - - if (lla->sdl_type == IFT_ETHER) - { - // Use the first ethernet MAC in the list. - // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. - a = (unsigned char*)&((lla)->sdl_data); - a += (lla)->sdl_nlen; - - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - { - continue; - } - - if (node_id) - { - memcpy(node_id, a, 6); - result = 1; - } - - // We found one. - break; - } - } - } - freeifaddrs(ifap); - - return result; -} - -#else - -// Linux version of the UUID generation code... -/* - * Get the ethernet hardware address, if we can find it... - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define HAVE_NETINET_IN_H -#ifdef HAVE_NETINET_IN_H -#include -#if !LL_DARWIN -#include -#endif -#endif - - // static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - int sd; - struct ifreq ifr, * ifrp; - struct ifconf ifc; - char buf[1024]; - int n, i; - unsigned char* a; - - /* - * BSD 4.4 defines the size of an ifreq to be - * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len - * However, under earlier systems, sa_len isn't present, so the size is - * just sizeof(struct ifreq) - */ -#ifdef HAVE_SA_LEN -#ifndef max -#define max(a,b) ((a) > (b) ? (a) : (b)) -#endif -#define ifreq_size(i) max(sizeof(struct ifreq),\ - sizeof((i).ifr_name)+(i).ifr_addr.sa_len) -#else -#define ifreq_size(i) sizeof(struct ifreq) -#endif /* HAVE_SA_LEN*/ - - sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (sd < 0) { - return -1; - } - memset(buf, 0, sizeof(buf)); - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) { - close(sd); - return -1; - } - n = ifc.ifc_len; - for (i = 0; i < n; i += ifreq_size(*ifr)) { - ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i); - strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ -#ifdef SIOCGIFHWADDR - if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) - continue; - a = (unsigned char*)&ifr.ifr_hwaddr.sa_data; -#else -#ifdef SIOCGENADDR - if (ioctl(sd, SIOCGENADDR, &ifr) < 0) - continue; - a = (unsigned char*)ifr.ifr_enaddr; -#else - /* - * XXX we don't have a way of getting the hardware - * address - */ - close(sd); - return 0; -#endif /* SIOCGENADDR */ -#endif /* SIOCGIFHWADDR */ - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - continue; - if (node_id) { - memcpy(node_id, a, 6); /* Flawfinder: ignore */ - close(sd); - return 1; - } - } - close(sd); - return 0; -} - -#endif - -S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2) -{ - // Compare two time values. - - if (t1->high < t2->high) return -1; - if (t1->high > t2->high) return 1; - if (t1->low < t2->low) return -1; - if (t1->low > t2->low) return 1; - return 0; -} - -void LLUUID::getSystemTime(uuid_time_t* timestamp) -{ - // Get system time with 100ns precision. Time is since Oct 15, 1582. -#if LL_WINDOWS - ULARGE_INTEGER time; - GetSystemTimeAsFileTime((FILETIME*)&time); - // NT keeps time in FILETIME format which is 100ns ticks since - // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. - // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) - // + 18 years and 5 leap days. - time.QuadPart += - (unsigned __int64)(1000 * 1000 * 10) // seconds - * (unsigned __int64)(60 * 60 * 24) // days - * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days - - timestamp->high = time.HighPart; - timestamp->low = time.LowPart; -#else - struct timeval tp; - gettimeofday(&tp, 0); - - // Offset between UUID formatted times and Unix formatted times. - // UUID UTC base time is October 15, 1582. - // Unix base time is January 1, 1970. - U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + - U64L(0x01B21DD213814000); - timestamp->high = (U32)(uuid_time >> 32); - timestamp->low = (U32)(uuid_time & 0xFFFFFFFF); -#endif -} - -void LLUUID::getCurrentTime(uuid_time_t* timestamp) -{ - // Get current time as 60 bit 100ns ticks since whenever. - // Compensate for the fact that real clock resolution is less - // than 100ns. - - const U32 uuids_per_tick = 1024; - - static uuid_time_t time_last; - static U32 uuids_this_tick; - static bool init = false; - - if (!init) { - getSystemTime(&time_last); - uuids_this_tick = uuids_per_tick; - init = true; - mMutex = new LLMutex(); - } - - uuid_time_t time_now = { 0,0 }; - - while (1) { - getSystemTime(&time_now); - - // if clock reading changed since last UUID generated - if (cmpTime(&time_last, &time_now)) { - // reset count of uuid's generated with this clock reading - uuids_this_tick = 0; - break; - } - if (uuids_this_tick < uuids_per_tick) { - uuids_this_tick++; - break; - } - // going too fast for our clock; spin - } - - time_last = time_now; - - if (uuids_this_tick != 0) { - if (time_now.low & 0x80000000) { - time_now.low += uuids_this_tick; - if (!(time_now.low & 0x80000000)) - time_now.high++; - } - else - time_now.low += uuids_this_tick; - } - - timestamp->high = time_now.high; - timestamp->low = time_now.low; -} - -void LLUUID::generate() -{ - // Create a UUID. - uuid_time_t timestamp; - - static unsigned char node_id[6]; /* Flawfinder: ignore */ - static int has_init = 0; - - // Create a UUID. - static uuid_time_t time_last = { 0,0 }; - static U16 clock_seq = 0; -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - static U32 seed = 0L; // dummy seed. reset it below -#endif - if (!has_init) - { - has_init = 1; - if (getNodeID(node_id) <= 0) - { - get_random_bytes(node_id, 6); - /* - * Set multicast bit, to prevent conflicts - * with IEEE 802 addresses obtained from - * network cards - */ - node_id[0] |= 0x80; - } - - getCurrentTime(&time_last); -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - seed = time_last.low; -#endif - -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); -#else - clock_seq = (U16)ll_rand(65536); -#endif - } - - // get current time - getCurrentTime(×tamp); - U16 our_clock_seq = clock_seq; - - // if clock hasn't changed or went backward, change clockseq - if (cmpTime(×tamp, &time_last) != 1) - { - LLMutexLock lock(mMutex); - clock_seq = (clock_seq + 1) & 0x3FFF; - if (clock_seq == 0) - clock_seq++; - our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time - } - - time_last = timestamp; - - memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */ - U32 tmp; - tmp = timestamp.low; - mData[3] = (unsigned char)tmp; - tmp >>= 8; - mData[2] = (unsigned char)tmp; - tmp >>= 8; - mData[1] = (unsigned char)tmp; - tmp >>= 8; - mData[0] = (unsigned char)tmp; - - tmp = (U16)timestamp.high; - mData[5] = (unsigned char)tmp; - tmp >>= 8; - mData[4] = (unsigned char)tmp; - - tmp = (timestamp.high >> 16) | 0x1000; - mData[7] = (unsigned char)tmp; - tmp >>= 8; - mData[6] = (unsigned char)tmp; - - tmp = our_clock_seq; - - mData[9] = (unsigned char)tmp; - tmp >>= 8; - mData[8] = (unsigned char)tmp; - - HBXXH128::digest(*this, (const void*)mData, 16); -} - -void LLUUID::generate(const std::string& hash_string) -{ - HBXXH128::digest(*this, hash_string); -} - -U32 LLUUID::getRandomSeed() -{ - static unsigned char seed[16]; /* Flawfinder: ignore */ - - getNodeID(&seed[0]); - - // Incorporate the pid into the seed to prevent - // processes that start on the same host at the same - // time from generating the same seed. - pid_t pid = LLApp::getPid(); - - seed[6] = (unsigned char)(pid >> 8); - seed[7] = (unsigned char)(pid); - getSystemTime((uuid_time_t*)(&seed[8])); - - U64 seed64 = HBXXH64::digest((const void*)seed, 16); - return U32(seed64) ^ U32(seed64 >> 32); -} - -bool LLUUID::parseUUID(const std::string& buf, LLUUID* value) -{ - if (buf.empty() || value == NULL) - { - return false; - } - - std::string temp(buf); - LLStringUtil::trim(temp); - if (LLUUID::validate(temp)) - { - value->set(temp); - return true; - } - return false; -} - -//static -LLUUID LLUUID::generateNewID(std::string hash_string) -{ - LLUUID new_id; - if (hash_string.empty()) - { - new_id.generate(); - } - else - { - new_id.generate(hash_string); - } - return new_id; -} - -LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const -{ - LLAssetID result; - if (isNull()) - { - result.setNull(); - } - else - { - combine(session, result); - } - return result; -} - -// Construct -LLUUID::LLUUID() -{ - setNull(); -} - - -// Faster than copying from memory -void LLUUID::setNull() -{ - U32* word = (U32*)mData; - word[0] = 0; - word[1] = 0; - word[2] = 0; - word[3] = 0; -} - - -// Compare -bool LLUUID::operator==(const LLUUID& rhs) const -{ - U32* tmp = (U32*)mData; - U32* rhstmp = (U32*)rhs.mData; - // Note: binary & to avoid branching - return - (tmp[0] == rhstmp[0]) & - (tmp[1] == rhstmp[1]) & - (tmp[2] == rhstmp[2]) & - (tmp[3] == rhstmp[3]); -} - - -bool LLUUID::operator!=(const LLUUID& rhs) const -{ - U32* tmp = (U32*)mData; - U32* rhstmp = (U32*)rhs.mData; - // Note: binary | to avoid branching - return - (tmp[0] != rhstmp[0]) | - (tmp[1] != rhstmp[1]) | - (tmp[2] != rhstmp[2]) | - (tmp[3] != rhstmp[3]); -} - -/* -// JC: This is dangerous. It allows UUIDs to be cast automatically -// to integers, among other things. Use isNull() or notNull(). - LLUUID::operator bool() const -{ - U32 *word = (U32 *)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; -} -*/ - -bool LLUUID::notNull() const -{ - U32* word = (U32*)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; -} - -// Faster than == LLUUID::null because doesn't require -// as much memory access. -bool LLUUID::isNull() const -{ - U32* word = (U32*)mData; - // If all bits are zero, return !0 == true - return !(word[0] | word[1] | word[2] | word[3]); -} - -LLUUID::LLUUID(const char* in_string) -{ - if (!in_string || in_string[0] == 0) - { - setNull(); - return; - } - - set(in_string); -} - -LLUUID::LLUUID(const std::string& in_string) -{ - if (in_string.empty()) - { - setNull(); - return; - } - - set(in_string); -} - -// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order -// IW: this will make me very sad -bool LLUUID::operator<(const LLUUID& rhs) const -{ - U32 i; - for (i = 0; i < (UUID_BYTES - 1); i++) - { - if (mData[i] != rhs.mData[i]) - { - return (mData[i] < rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); -} - -bool LLUUID::operator>(const LLUUID& rhs) const -{ - U32 i; - for (i = 0; i < (UUID_BYTES - 1); i++) - { - if (mData[i] != rhs.mData[i]) - { - return (mData[i] > rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); -} - -U16 LLUUID::getCRC16() const -{ - // A UUID is 16 bytes, or 8 shorts. - U16* short_data = (U16*)mData; - U16 out = 0; - out += short_data[0]; - out += short_data[1]; - out += short_data[2]; - out += short_data[3]; - out += short_data[4]; - out += short_data[5]; - out += short_data[6]; - out += short_data[7]; - return out; -} - -U32 LLUUID::getCRC32() const -{ - U32* tmp = (U32*)mData; - return tmp[0] + tmp[1] + tmp[2] + tmp[3]; -} +/** + * @file lluuid.cpp + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + + // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. +#if LL_WINDOWS +#include "llwin32headers.h" +// ugh, this is ugly. We need to straighten out our linking for this library +#pragma comment(lib, "IPHLPAPI.lib") +#include +#endif + +#include "llapp.h" +#include "lldefs.h" +#include "llerror.h" + +#include "lluuid.h" +#include "llerror.h" +#include "llrand.h" +#include "llstring.h" +#include "lltimer.h" +#include "llthread.h" +#include "llmutex.h" +#include "llmd5.h" +#include "hbxxh.h" + +const LLUUID LLUUID::null; +const LLTransactionID LLTransactionID::tnull; + +// static +LLMutex* LLUUID::mMutex = NULL; + + + +/* + +NOT DONE YET!!! + +static char BASE85_TABLE[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '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', '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' +}; + + +void encode( char * fiveChars, unsigned int word ) throw( ) +{ +for( int ix = 0; ix < 5; ++ix ) { +fiveChars[4-ix] = encodeTable[ word % 85]; +word /= 85; +} +} + +To decode: +unsigned int decode( char const * fiveChars ) throw( bad_input_data ) +{ +unsigned int ret = 0; +for( int ix = 0; ix < 5; ++ix ) { +char * s = strchr( encodeTable, fiveChars[ ix ] ); +if( s == 0 ) LLTHROW(bad_input_data()); +ret = ret * 85 + (s-encodeTable); +} +return ret; +} + +void LLUUID::toBase85(char* out) +{ + U32* me = (U32*)&(mData[0]); + for(S32 i = 0; i < 4; ++i) + { + char* o = &out[i*i]; + for(S32 j = 0; j < 5; ++j) + { + o[4-j] = BASE85_TABLE[ me[i] % 85]; + word /= 85; + } + } +} + +unsigned int decode( char const * fiveChars ) throw( bad_input_data ) +{ + unsigned int ret = 0; + for( S32 ix = 0; ix < 5; ++ix ) + { + char * s = strchr( encodeTable, fiveChars[ ix ] ); + ret = ret * 85 + (s-encodeTable); + } + return ret; +} +*/ + +#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0 +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR +/** + * @brief a global for + */ +static U64 sJankyRandomSeed(LLUUID::getRandomSeed()); + +/** + * @brief generate a random U32. + */ +U32 janky_fast_random_bytes() +{ + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)sJankyRandomSeed; +} + +/** + * @brief generate a random U32 from [0, val) + */ +U32 janky_fast_random_byes_range(U32 val) +{ + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)(sJankyRandomSeed) % val; +} + +/** + * @brief generate a random U32 from [0, val) + */ +U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) +{ + seed = U64L(1664525) * (U64)(seed)+U64L(1013904223); + return (U32)(seed) % val; +} +#endif + +// Common to all UUID implementations +void LLUUID::toString(std::string& out) const +{ + out = llformat( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (U8)(mData[0]), + (U8)(mData[1]), + (U8)(mData[2]), + (U8)(mData[3]), + (U8)(mData[4]), + (U8)(mData[5]), + (U8)(mData[6]), + (U8)(mData[7]), + (U8)(mData[8]), + (U8)(mData[9]), + (U8)(mData[10]), + (U8)(mData[11]), + (U8)(mData[12]), + (U8)(mData[13]), + (U8)(mData[14]), + (U8)(mData[15])); +} + +// *TODO: deprecate +void LLUUID::toString(char* out) const +{ + std::string buffer; + toString(buffer); + strcpy(out, buffer.c_str()); /* Flawfinder: ignore */ +} + +void LLUUID::toCompressedString(std::string& out) const +{ + char bytes[UUID_BYTES + 1]; + memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ + bytes[UUID_BYTES] = '\0'; + out.assign(bytes, UUID_BYTES); +} + +// *TODO: deprecate +void LLUUID::toCompressedString(char* out) const +{ + memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ + out[UUID_BYTES] = '\0'; +} + +std::string LLUUID::getString() const +{ + return asString(); +} + +std::string LLUUID::asString() const +{ + std::string str; + toString(str); + return str; +} + +bool LLUUID::set(const char* in_string, bool emit) +{ + return set(ll_safe_string(in_string), emit); +} + +bool LLUUID::set(const std::string& in_string, bool emit) +{ + bool broken_format = false; + + // empty strings should make NULL uuid + if (in_string.empty()) + { + setNull(); + return true; + } + + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + // Shouldn't see any of these any more + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + if (emit) + { + LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; + } + broken_format = true; + } + else + { + // Bad UUID string. Spam as INFO, as most cases we don't care. + if (emit) + { + //don't spam the logs because a resident can't spell. + LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; + } + setNull(); + return false; + } + } + + U8 cur_pos = 0; + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + mData[i] = 0; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return false; + } + + mData[i] = mData[i] << 4; + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return false; + } + cur_pos++; + } + + return true; +} + +bool LLUUID::validate(const std::string& in_string) +{ + bool broken_format = false; + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + broken_format = true; + } + else + { + return false; + } + } + + U8 cur_pos = 0; + for (U32 i = 0; i < 16; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return false; + } + + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return false; + } + cur_pos++; + } + return true; +} + +const LLUUID& LLUUID::operator^=(const LLUUID& rhs) +{ + U32* me = (U32*)&(mData[0]); + const U32* other = (U32*)&(rhs.mData[0]); + for (S32 i = 0; i < 4; ++i) + { + me[i] = me[i] ^ other[i]; + } + return *this; +} + +LLUUID LLUUID::operator^(const LLUUID& rhs) const +{ + LLUUID id(*this); + id ^= rhs; + return id; +} + +// WARNING: this algorithm SHALL NOT be changed. It is also used by the server +// and plays a role in some assets validation (e.g. clothing items). Changing +// it would cause invalid assets. +void LLUUID::combine(const LLUUID& other, LLUUID& result) const +{ + LLMD5 md5_uuid; + md5_uuid.update((unsigned char*)mData, 16); + md5_uuid.update((unsigned char*)other.mData, 16); + md5_uuid.finalize(); + md5_uuid.raw_digest(result.mData); +} + +LLUUID LLUUID::combine(const LLUUID& other) const +{ + LLUUID combination; + combine(other, combination); + return combination; +} + +std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) +{ + std::string uuid_str; + uuid.toString(uuid_str); + s << uuid_str; + return s; +} + +std::istream& operator>>(std::istream& s, LLUUID& uuid) +{ + U32 i; + char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ + for (i = 0; i < UUID_STR_LENGTH - 1; i++) + { + s >> uuid_str[i]; + } + uuid_str[i] = '\0'; + uuid.set(std::string(uuid_str)); + return s; +} + +static void get_random_bytes(void* buf, int nbytes) +{ + int i; + char* cp = (char*)buf; + + // *NOTE: If we are not using the janky generator ll_rand() + // generates at least 3 good bytes of data since it is 0 to + // RAND_MAX. This could be made more efficient by copying all the + // bytes. + for (i = 0; i < nbytes; i++) +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + * cp++ = janky_fast_random_bytes() & 0xFF; +#else + * cp++ = ll_rand() & 0xFF; +#endif + return; +} + +#if LL_WINDOWS + +typedef struct _ASTAT_ +{ + ADAPTER_STATUS adapt; + NAME_BUFFER NameBuff[30]; +}ASTAT, * PASTAT; + +// static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + ASTAT Adapter; + NCB Ncb; + UCHAR uRetCode; + LANA_ENUM lenum; + int i; + int retval = 0; + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBENUM; + Ncb.ncb_buffer = (UCHAR*)&lenum; + Ncb.ncb_length = sizeof(lenum); + uRetCode = Netbios(&Ncb); + + for (i = 0; i < lenum.length; i++) + { + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBRESET; + Ncb.ncb_lana_num = lenum.lana[i]; + + uRetCode = Netbios(&Ncb); + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBASTAT; + Ncb.ncb_lana_num = lenum.lana[i]; + + strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */ + Ncb.ncb_buffer = (unsigned char*)&Adapter; + Ncb.ncb_length = sizeof(Adapter); + + uRetCode = Netbios(&Ncb); + if (uRetCode == 0) + { + memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */ + retval = 1; + } + } + return retval; +} + +#elif LL_DARWIN +// macOS version of the UUID generation code... +/* + * Get an ethernet hardware address, if we can find it... + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + // static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + int i; + unsigned char* a = NULL; + struct ifaddrs* ifap, * ifa; + int rv; + S32 result = 0; + + if ((rv = getifaddrs(&ifap)) == -1) + { + return -1; + } + if (ifap == NULL) + { + return -1; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) + { + // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); + for (i = 0; i < ifa->ifa_addr->sa_len; i++) + { + // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); + } + // printf("\n"); + + if (ifa->ifa_addr->sa_family == AF_LINK) + { + // This is a link-level address + struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr; + + // printf("\tLink level address, type %02X\n", lla->sdl_type); + + if (lla->sdl_type == IFT_ETHER) + { + // Use the first ethernet MAC in the list. + // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. + a = (unsigned char*)&((lla)->sdl_data); + a += (lla)->sdl_nlen; + + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + { + continue; + } + + if (node_id) + { + memcpy(node_id, a, 6); + result = 1; + } + + // We found one. + break; + } + } + } + freeifaddrs(ifap); + + return result; +} + +#else + +// Linux version of the UUID generation code... +/* + * Get the ethernet hardware address, if we can find it... + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_NETINET_IN_H +#ifdef HAVE_NETINET_IN_H +#include +#if !LL_DARWIN +#include +#endif +#endif + + // static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + int sd; + struct ifreq ifr, * ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char* a; + + /* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i += ifreq_size(*ifr)) { + ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char*)&ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char*)ifr.ifr_enaddr; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); /* Flawfinder: ignore */ + close(sd); + return 1; + } + } + close(sd); + return 0; +} + +#endif + +S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2) +{ + // Compare two time values. + + if (t1->high < t2->high) return -1; + if (t1->high > t2->high) return 1; + if (t1->low < t2->low) return -1; + if (t1->low > t2->low) return 1; + return 0; +} + +void LLUUID::getSystemTime(uuid_time_t* timestamp) +{ + // Get system time with 100ns precision. Time is since Oct 15, 1582. +#if LL_WINDOWS + ULARGE_INTEGER time; + GetSystemTimeAsFileTime((FILETIME*)&time); + // NT keeps time in FILETIME format which is 100ns ticks since + // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. + // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) + // + 18 years and 5 leap days. + time.QuadPart += + (unsigned __int64)(1000 * 1000 * 10) // seconds + * (unsigned __int64)(60 * 60 * 24) // days + * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days + + timestamp->high = time.HighPart; + timestamp->low = time.LowPart; +#else + struct timeval tp; + gettimeofday(&tp, 0); + + // Offset between UUID formatted times and Unix formatted times. + // UUID UTC base time is October 15, 1582. + // Unix base time is January 1, 1970. + U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + + U64L(0x01B21DD213814000); + timestamp->high = (U32)(uuid_time >> 32); + timestamp->low = (U32)(uuid_time & 0xFFFFFFFF); +#endif +} + +void LLUUID::getCurrentTime(uuid_time_t* timestamp) +{ + // Get current time as 60 bit 100ns ticks since whenever. + // Compensate for the fact that real clock resolution is less + // than 100ns. + + const U32 uuids_per_tick = 1024; + + static uuid_time_t time_last; + static U32 uuids_this_tick; + static bool init = false; + + if (!init) { + getSystemTime(&time_last); + uuids_this_tick = uuids_per_tick; + init = true; + mMutex = new LLMutex(); + } + + uuid_time_t time_now = { 0,0 }; + + while (1) { + getSystemTime(&time_now); + + // if clock reading changed since last UUID generated + if (cmpTime(&time_last, &time_now)) { + // reset count of uuid's generated with this clock reading + uuids_this_tick = 0; + break; + } + if (uuids_this_tick < uuids_per_tick) { + uuids_this_tick++; + break; + } + // going too fast for our clock; spin + } + + time_last = time_now; + + if (uuids_this_tick != 0) { + if (time_now.low & 0x80000000) { + time_now.low += uuids_this_tick; + if (!(time_now.low & 0x80000000)) + time_now.high++; + } + else + time_now.low += uuids_this_tick; + } + + timestamp->high = time_now.high; + timestamp->low = time_now.low; +} + +void LLUUID::generate() +{ + // Create a UUID. + uuid_time_t timestamp; + + static unsigned char node_id[6]; /* Flawfinder: ignore */ + static int has_init = 0; + + // Create a UUID. + static uuid_time_t time_last = { 0,0 }; + static U16 clock_seq = 0; +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + static U32 seed = 0L; // dummy seed. reset it below +#endif + if (!has_init) + { + has_init = 1; + if (getNodeID(node_id) <= 0) + { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x80; + } + + getCurrentTime(&time_last); +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + seed = time_last.low; +#endif + +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); +#else + clock_seq = (U16)ll_rand(65536); +#endif + } + + // get current time + getCurrentTime(×tamp); + U16 our_clock_seq = clock_seq; + + // if clock hasn't changed or went backward, change clockseq + if (cmpTime(×tamp, &time_last) != 1) + { + LLMutexLock lock(mMutex); + clock_seq = (clock_seq + 1) & 0x3FFF; + if (clock_seq == 0) + clock_seq++; + our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time + } + + time_last = timestamp; + + memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */ + U32 tmp; + tmp = timestamp.low; + mData[3] = (unsigned char)tmp; + tmp >>= 8; + mData[2] = (unsigned char)tmp; + tmp >>= 8; + mData[1] = (unsigned char)tmp; + tmp >>= 8; + mData[0] = (unsigned char)tmp; + + tmp = (U16)timestamp.high; + mData[5] = (unsigned char)tmp; + tmp >>= 8; + mData[4] = (unsigned char)tmp; + + tmp = (timestamp.high >> 16) | 0x1000; + mData[7] = (unsigned char)tmp; + tmp >>= 8; + mData[6] = (unsigned char)tmp; + + tmp = our_clock_seq; + + mData[9] = (unsigned char)tmp; + tmp >>= 8; + mData[8] = (unsigned char)tmp; + + HBXXH128::digest(*this, (const void*)mData, 16); +} + +void LLUUID::generate(const std::string& hash_string) +{ + HBXXH128::digest(*this, hash_string); +} + +U32 LLUUID::getRandomSeed() +{ + static unsigned char seed[16]; /* Flawfinder: ignore */ + + getNodeID(&seed[0]); + + // Incorporate the pid into the seed to prevent + // processes that start on the same host at the same + // time from generating the same seed. + pid_t pid = LLApp::getPid(); + + seed[6] = (unsigned char)(pid >> 8); + seed[7] = (unsigned char)(pid); + getSystemTime((uuid_time_t*)(&seed[8])); + + U64 seed64 = HBXXH64::digest((const void*)seed, 16); + return U32(seed64) ^ U32(seed64 >> 32); +} + +bool LLUUID::parseUUID(const std::string& buf, LLUUID* value) +{ + if (buf.empty() || value == NULL) + { + return false; + } + + std::string temp(buf); + LLStringUtil::trim(temp); + if (LLUUID::validate(temp)) + { + value->set(temp); + return true; + } + return false; +} + +//static +LLUUID LLUUID::generateNewID(std::string hash_string) +{ + LLUUID new_id; + if (hash_string.empty()) + { + new_id.generate(); + } + else + { + new_id.generate(hash_string); + } + return new_id; +} + +LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const +{ + LLAssetID result; + if (isNull()) + { + result.setNull(); + } + else + { + combine(session, result); + } + return result; +} + +// Construct +LLUUID::LLUUID() +{ + setNull(); +} + + +// Faster than copying from memory +void LLUUID::setNull() +{ + U32* word = (U32*)mData; + word[0] = 0; + word[1] = 0; + word[2] = 0; + word[3] = 0; +} + + +// Compare +bool LLUUID::operator==(const LLUUID& rhs) const +{ + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary & to avoid branching + return + (tmp[0] == rhstmp[0]) & + (tmp[1] == rhstmp[1]) & + (tmp[2] == rhstmp[2]) & + (tmp[3] == rhstmp[3]); +} + + +bool LLUUID::operator!=(const LLUUID& rhs) const +{ + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary | to avoid branching + return + (tmp[0] != rhstmp[0]) | + (tmp[1] != rhstmp[1]) | + (tmp[2] != rhstmp[2]) | + (tmp[3] != rhstmp[3]); +} + +/* +// JC: This is dangerous. It allows UUIDs to be cast automatically +// to integers, among other things. Use isNull() or notNull(). + LLUUID::operator bool() const +{ + U32 *word = (U32 *)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; +} +*/ + +bool LLUUID::notNull() const +{ + U32* word = (U32*)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; +} + +// Faster than == LLUUID::null because doesn't require +// as much memory access. +bool LLUUID::isNull() const +{ + U32* word = (U32*)mData; + // If all bits are zero, return !0 == true + return !(word[0] | word[1] | word[2] | word[3]); +} + +LLUUID::LLUUID(const char* in_string) +{ + if (!in_string || in_string[0] == 0) + { + setNull(); + return; + } + + set(in_string); +} + +LLUUID::LLUUID(const std::string& in_string) +{ + if (in_string.empty()) + { + setNull(); + return; + } + + set(in_string); +} + +// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order +// IW: this will make me very sad +bool LLUUID::operator<(const LLUUID& rhs) const +{ + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] < rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); +} + +bool LLUUID::operator>(const LLUUID& rhs) const +{ + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] > rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); +} + +U16 LLUUID::getCRC16() const +{ + // A UUID is 16 bytes, or 8 shorts. + U16* short_data = (U16*)mData; + U16 out = 0; + out += short_data[0]; + out += short_data[1]; + out += short_data[2]; + out += short_data[3]; + out += short_data[4]; + out += short_data[5]; + out += short_data[6]; + out += short_data[7]; + return out; +} + +U32 LLUUID::getCRC32() const +{ + U32* tmp = (U32*)mData; + return tmp[0] + tmp[1] + tmp[2] + tmp[3]; +} diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 526a79f3a7..68c4b05fdc 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -1,194 +1,194 @@ -/** - * @file lluuid.h - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLUUID_H -#define LL_LLUUID_H - -#include -#include -#include -#include "stdtypes.h" -#include "llpreprocessor.h" -#include - -class LLMutex; - -const S32 UUID_BYTES = 16; -const S32 UUID_WORDS = 4; -const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below -const S32 UUID_STR_SIZE = 37; -const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. - -struct uuid_time_t { - U32 high; - U32 low; - }; - -class LL_COMMON_API LLUUID -{ -public: - // - // CREATORS - // - LLUUID(); - explicit LLUUID(const char *in_string); // Convert from string. - explicit LLUUID(const std::string& in_string); // Convert from string. - ~LLUUID() = default; - - // - // MANIPULATORS - // - void generate(); // Generate a new UUID - void generate(const std::string& stream); //Generate a new UUID based on hash of input stream - - static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc. - - bool set(const char *in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings - bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings - void setNull(); // Faster than setting to LLUUID::null. - - S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2); - static void getSystemTime(uuid_time_t *timestamp); - void getCurrentTime(uuid_time_t *timestamp); - - // - // ACCESSORS - // - bool isNull() const; // Faster than comparing to LLUUID::null. - bool notNull() const; // Faster than comparing to LLUUID::null. - // JC: This is dangerous. It allows UUIDs to be cast automatically - // to integers, among other things. Use isNull() or notNull(). - // operator bool() const; - - // JC: These must return real bool's (not BOOLs) or else use of the STL - // will generate bool-to-int performance warnings. - bool operator==(const LLUUID &rhs) const; - bool operator!=(const LLUUID &rhs) const; - bool operator<(const LLUUID &rhs) const; - bool operator>(const LLUUID &rhs) const; - - // xor functions. Useful since any two random uuids xored together - // will yield a determinate third random unique id that can be - // used as a key in a single uuid that represents 2. - const LLUUID& operator^=(const LLUUID& rhs); - LLUUID operator^(const LLUUID& rhs) const; - - // similar to functions above, but not invertible - // yields a third random UUID that can be reproduced from the two inputs - // but which, given the result and one of the inputs can't be used to - // deduce the other input - LLUUID combine(const LLUUID& other) const; - void combine(const LLUUID& other, LLUUID& result) const; - - friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); - friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); - - void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) - void toString(std::string& out) const; - void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0) - void toCompressedString(std::string& out) const; - - std::string asString() const; - std::string getString() const; - - U16 getCRC16() const; - U32 getCRC32() const; - - // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long - // words. HB - inline U64 getDigest64() const - { - U64* tmp = (U64*)mData; - return tmp[0] ^ tmp[1]; - } - - static bool validate(const std::string& in_string); // Validate that the UUID string is legal. - - static const LLUUID null; - static LLMutex * mMutex; - - static U32 getRandomSeed(); - static S32 getNodeID(unsigned char * node_id); - - static bool parseUUID(const std::string& buf, LLUUID* value); - - U8 mData[UUID_BYTES]; -}; -static_assert(std::is_trivially_copyable::value, "LLUUID must be trivial copy"); -static_assert(std::is_trivially_move_assignable::value, "LLUUID must be trivial move"); -static_assert(std::is_standard_layout::value, "LLUUID must be a standard layout type"); - -typedef std::vector uuid_vec_t; -typedef std::set uuid_set_t; - -// Helper structure for ordering lluuids in stl containers. eg: -// std::map widget_map; -// -// (isn't this the default behavior anyway? I think we could -// everywhere replace these with uuid_set_t, but someone should -// verify.) -struct lluuid_less -{ - bool operator()(const LLUUID& lhs, const LLUUID& rhs) const - { - return lhs < rhs; - } -}; - -typedef std::set uuid_list_t; -/* - * Sub-classes for keeping transaction IDs and asset IDs - * straight. - */ -typedef LLUUID LLAssetID; - -class LL_COMMON_API LLTransactionID : public LLUUID -{ -public: - LLTransactionID() : LLUUID() { } - - static const LLTransactionID tnull; - LLAssetID makeAssetID(const LLUUID& session) const; -}; - -// std::hash implementation for LLUUID -namespace std -{ - template<> struct hash - { - inline size_t operator()(const LLUUID& id) const noexcept - { - return (size_t)id.getDigest64(); - } - }; -} - -// For use with boost containers. -inline size_t hash_value(const LLUUID& id) noexcept -{ - return (size_t)id.getDigest64(); -} - -#endif // LL_LLUUID_H +/** + * @file lluuid.h + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLUUID_H +#define LL_LLUUID_H + +#include +#include +#include +#include "stdtypes.h" +#include "llpreprocessor.h" +#include + +class LLMutex; + +const S32 UUID_BYTES = 16; +const S32 UUID_WORDS = 4; +const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below +const S32 UUID_STR_SIZE = 37; +const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. + +struct uuid_time_t { + U32 high; + U32 low; + }; + +class LL_COMMON_API LLUUID +{ +public: + // + // CREATORS + // + LLUUID(); + explicit LLUUID(const char *in_string); // Convert from string. + explicit LLUUID(const std::string& in_string); // Convert from string. + ~LLUUID() = default; + + // + // MANIPULATORS + // + void generate(); // Generate a new UUID + void generate(const std::string& stream); //Generate a new UUID based on hash of input stream + + static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc. + + bool set(const char *in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings + bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings + void setNull(); // Faster than setting to LLUUID::null. + + S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2); + static void getSystemTime(uuid_time_t *timestamp); + void getCurrentTime(uuid_time_t *timestamp); + + // + // ACCESSORS + // + bool isNull() const; // Faster than comparing to LLUUID::null. + bool notNull() const; // Faster than comparing to LLUUID::null. + // JC: This is dangerous. It allows UUIDs to be cast automatically + // to integers, among other things. Use isNull() or notNull(). + // operator bool() const; + + // JC: These must return real bool's (not BOOLs) or else use of the STL + // will generate bool-to-int performance warnings. + bool operator==(const LLUUID &rhs) const; + bool operator!=(const LLUUID &rhs) const; + bool operator<(const LLUUID &rhs) const; + bool operator>(const LLUUID &rhs) const; + + // xor functions. Useful since any two random uuids xored together + // will yield a determinate third random unique id that can be + // used as a key in a single uuid that represents 2. + const LLUUID& operator^=(const LLUUID& rhs); + LLUUID operator^(const LLUUID& rhs) const; + + // similar to functions above, but not invertible + // yields a third random UUID that can be reproduced from the two inputs + // but which, given the result and one of the inputs can't be used to + // deduce the other input + LLUUID combine(const LLUUID& other) const; + void combine(const LLUUID& other, LLUUID& result) const; + + friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); + friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); + + void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) + void toString(std::string& out) const; + void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0) + void toCompressedString(std::string& out) const; + + std::string asString() const; + std::string getString() const; + + U16 getCRC16() const; + U32 getCRC32() const; + + // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long + // words. HB + inline U64 getDigest64() const + { + U64* tmp = (U64*)mData; + return tmp[0] ^ tmp[1]; + } + + static bool validate(const std::string& in_string); // Validate that the UUID string is legal. + + static const LLUUID null; + static LLMutex * mMutex; + + static U32 getRandomSeed(); + static S32 getNodeID(unsigned char * node_id); + + static bool parseUUID(const std::string& buf, LLUUID* value); + + U8 mData[UUID_BYTES]; +}; +static_assert(std::is_trivially_copyable::value, "LLUUID must be trivial copy"); +static_assert(std::is_trivially_move_assignable::value, "LLUUID must be trivial move"); +static_assert(std::is_standard_layout::value, "LLUUID must be a standard layout type"); + +typedef std::vector uuid_vec_t; +typedef std::set uuid_set_t; + +// Helper structure for ordering lluuids in stl containers. eg: +// std::map widget_map; +// +// (isn't this the default behavior anyway? I think we could +// everywhere replace these with uuid_set_t, but someone should +// verify.) +struct lluuid_less +{ + bool operator()(const LLUUID& lhs, const LLUUID& rhs) const + { + return lhs < rhs; + } +}; + +typedef std::set uuid_list_t; +/* + * Sub-classes for keeping transaction IDs and asset IDs + * straight. + */ +typedef LLUUID LLAssetID; + +class LL_COMMON_API LLTransactionID : public LLUUID +{ +public: + LLTransactionID() : LLUUID() { } + + static const LLTransactionID tnull; + LLAssetID makeAssetID(const LLUUID& session) const; +}; + +// std::hash implementation for LLUUID +namespace std +{ + template<> struct hash + { + inline size_t operator()(const LLUUID& id) const noexcept + { + return (size_t)id.getDigest64(); + } + }; +} + +// For use with boost containers. +inline size_t hash_value(const LLUUID& id) noexcept +{ + return (size_t)id.getDigest64(); +} + +#endif // LL_LLUUID_H diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 22a922c94b..b751c95679 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -1,398 +1,398 @@ -/** - * @file llworkerthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llworkerthread.h" -#include "llstl.h" - -#if USE_FRAME_CALLBACK_MANAGER -#include "llframecallbackmanager.h" -#endif - -//============================================================================ -// Run on MAIN thread - -LLWorkerThread::LLWorkerThread(const std::string& name, bool threaded, bool should_pause) : - LLQueuedThread(name, threaded, should_pause) -{ - mDeleteMutex = new LLMutex(); - - if(!mLocalAPRFilePoolp) - { - mLocalAPRFilePoolp = new LLVolatileAPRPool() ; - } -} - -LLWorkerThread::~LLWorkerThread() -{ - // Delete any workers in the delete queue (should be safe - had better be!) - if (!mDeleteList.empty()) - { - LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() - << " entries in delete list." << LL_ENDL; - } - - delete mDeleteMutex; - - // ~LLQueuedThread() will be called here -} - -//called only in destructor. -void LLWorkerThread::clearDeleteList() -{ - // Delete any workers in the delete queue (should be safe - had better be!) - if (!mDeleteList.empty()) - { - LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() - << " entries in delete list." << LL_ENDL; - - mDeleteMutex->lock(); - for (LLWorkerClass* worker : mDeleteList) - { - worker->mRequestHandle = LLWorkerThread::nullHandle(); - worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); - worker->clearFlags(LLWorkerClass::WCF_WORKING); - delete worker; - } - mDeleteList.clear() ; - mDeleteMutex->unlock() ; - } -} - -// virtual -size_t LLWorkerThread::update(F32 max_time_ms) -{ - auto res = LLQueuedThread::update(max_time_ms); - // Delete scheduled workers - std::vector delete_list; - std::vector abort_list; - mDeleteMutex->lock(); - for (delete_list_t::iterator iter = mDeleteList.begin(); - iter != mDeleteList.end(); ) - { - delete_list_t::iterator curiter = iter++; - LLWorkerClass* worker = *curiter; - if (worker->deleteOK()) - { - if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED)) - { - worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED); - delete_list.push_back(worker); - mDeleteList.erase(curiter); - } - else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED)) - { - abort_list.push_back(worker); - } - } - } - mDeleteMutex->unlock(); - // abort and delete after releasing mutex - for (LLWorkerClass* worker : abort_list) - { - worker->abortWork(false); - } - for (LLWorkerClass* worker : delete_list) - { - if (worker->mRequestHandle) - { - // Finished but not completed - completeRequest(worker->mRequestHandle); - worker->mRequestHandle = LLWorkerThread::nullHandle(); - worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); - } - delete worker; - } - // delete and aborted entries mean there's still work to do - res += delete_list.size() + abort_list.size(); - return res; -} - -//---------------------------------------------------------------------------- - -LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param) -{ - handle_t handle = generateHandle(); - - WorkRequest* req = new WorkRequest(handle, workerclass, param); - - bool res = addRequest(req); - if (!res) - { - LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - handle = nullHandle(); - } - - return handle; -} - -void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass) -{ - mDeleteMutex->lock(); - mDeleteList.push_back(workerclass); - mDeleteMutex->unlock(); -} - -//============================================================================ -// Runs on its OWN thread - -LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) : - LLQueuedThread::QueuedRequest(handle), - mWorkerClass(workerclass), - mParam(param) -{ -} - -LLWorkerThread::WorkRequest::~WorkRequest() -{ -} - -// virtual (required for access by LLWorkerThread) -void LLWorkerThread::WorkRequest::deleteRequest() -{ - LLQueuedThread::QueuedRequest::deleteRequest(); -} - -// virtual -bool LLWorkerThread::WorkRequest::processRequest() -{ - LL_PROFILE_ZONE_SCOPED; - LLWorkerClass* workerclass = getWorkerClass(); - workerclass->setWorking(true); - bool complete = workerclass->doWork(getParam()); - workerclass->setWorking(false); - return complete; -} - -// virtual -void LLWorkerThread::WorkRequest::finishRequest(bool completed) -{ - LL_PROFILE_ZONE_SCOPED; - LLWorkerClass* workerclass = getWorkerClass(); - workerclass->finishWork(getParam(), completed); - U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED); - workerclass->setFlags(flags); -} - -//============================================================================ -// LLWorkerClass:: operates in main thread - -LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name) - : mWorkerThread(workerthread), - mWorkerClassName(name), - mRequestHandle(LLWorkerThread::nullHandle()), - mMutex(), - mWorkFlags(0) -{ - if (!mWorkerThread) - { - LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL; - } -} - -LLWorkerClass::~LLWorkerClass() -{ - llassert_always(!(mWorkFlags & WCF_WORKING)); - llassert_always(mWorkFlags & WCF_DELETE_REQUESTED); - llassert_always(!mMutex.isLocked()); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); - if (!workreq) - { - LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL; - } - if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED && - workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE) - { - LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL; - } - } -} - -void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread) -{ - mMutex.lock(); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL; - } - mWorkerThread = workerthread; - mMutex.unlock(); -} - -//---------------------------------------------------------------------------- - -//virtual -void LLWorkerClass::finishWork(S32 param, bool success) -{ -} - -//virtual -bool LLWorkerClass::deleteOK() -{ - return true; // default always OK -} - -//---------------------------------------------------------------------------- - -// Called from worker thread -void LLWorkerClass::setWorking(bool working) -{ - mMutex.lock(); - if (working) - { - llassert_always(!(mWorkFlags & WCF_WORKING)); - setFlags(WCF_WORKING); - } - else - { - llassert_always((mWorkFlags & WCF_WORKING)); - clearFlags(WCF_WORKING); - } - mMutex.unlock(); -} - -//---------------------------------------------------------------------------- - -bool LLWorkerClass::yield() -{ - LLThread::yield(); - mWorkerThread->checkPause(); - bool res; - mMutex.lock(); - res = (getFlags() & WCF_ABORT_REQUESTED) != 0; - mMutex.unlock(); - return res; -} - -//---------------------------------------------------------------------------- - -// calls startWork, adds doWork() to queue -void LLWorkerClass::addWork(S32 param) -{ - mMutex.lock(); - llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK))); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL; - } -#if _DEBUG -// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL; -#endif - startWork(param); - clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED); - setFlags(WCF_HAVE_WORK); - mRequestHandle = mWorkerThread->addWorkRequest(this, param); - mMutex.unlock(); -} - -void LLWorkerClass::abortWork(bool autocomplete) -{ - mMutex.lock(); -#if _DEBUG -// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle); -// if (workreq) -// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL; -#endif - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - mWorkerThread->abortRequest(mRequestHandle, autocomplete); - setFlags(WCF_ABORT_REQUESTED); - } - mMutex.unlock(); -} - -// if doWork is complete or aborted, call endWork() and return true -bool LLWorkerClass::checkWork(bool aborting) -{ - LLMutexLock lock(&mMutex); - bool complete = false, abort = false; - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); - if(!workreq) - { - if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running - { - mRequestHandle = LLWorkerThread::nullHandle(); - clearFlags(WCF_HAVE_WORK); - } - else - { - llassert_always(workreq); - } - return true ; - } - - LLQueuedThread::status_t status = workreq->getStatus(); - if (status == LLWorkerThread::STATUS_ABORTED) - { - complete = true; - abort = true; - } - else if (status == LLWorkerThread::STATUS_COMPLETE) - { - complete = true; - } - else - { - llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT)); - } - if (complete) - { - llassert_always(!(getFlags(WCF_WORKING))); - endWork(workreq->getParam(), abort); - mWorkerThread->completeRequest(mRequestHandle); - mRequestHandle = LLWorkerThread::nullHandle(); - clearFlags(WCF_HAVE_WORK); - } - } - else - { - complete = true; - } - return complete; -} - -void LLWorkerClass::scheduleDelete() -{ - bool do_delete = false; - mMutex.lock(); - if (!(getFlags(WCF_DELETE_REQUESTED))) - { - setFlags(WCF_DELETE_REQUESTED); - do_delete = true; - } - mMutex.unlock(); - if (do_delete) - { - mWorkerThread->deleteWorker(this); - } -} - -//============================================================================ - +/** + * @file llworkerthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llworkerthread.h" +#include "llstl.h" + +#if USE_FRAME_CALLBACK_MANAGER +#include "llframecallbackmanager.h" +#endif + +//============================================================================ +// Run on MAIN thread + +LLWorkerThread::LLWorkerThread(const std::string& name, bool threaded, bool should_pause) : + LLQueuedThread(name, threaded, should_pause) +{ + mDeleteMutex = new LLMutex(); + + if(!mLocalAPRFilePoolp) + { + mLocalAPRFilePoolp = new LLVolatileAPRPool() ; + } +} + +LLWorkerThread::~LLWorkerThread() +{ + // Delete any workers in the delete queue (should be safe - had better be!) + if (!mDeleteList.empty()) + { + LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() + << " entries in delete list." << LL_ENDL; + } + + delete mDeleteMutex; + + // ~LLQueuedThread() will be called here +} + +//called only in destructor. +void LLWorkerThread::clearDeleteList() +{ + // Delete any workers in the delete queue (should be safe - had better be!) + if (!mDeleteList.empty()) + { + LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() + << " entries in delete list." << LL_ENDL; + + mDeleteMutex->lock(); + for (LLWorkerClass* worker : mDeleteList) + { + worker->mRequestHandle = LLWorkerThread::nullHandle(); + worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); + worker->clearFlags(LLWorkerClass::WCF_WORKING); + delete worker; + } + mDeleteList.clear() ; + mDeleteMutex->unlock() ; + } +} + +// virtual +size_t LLWorkerThread::update(F32 max_time_ms) +{ + auto res = LLQueuedThread::update(max_time_ms); + // Delete scheduled workers + std::vector delete_list; + std::vector abort_list; + mDeleteMutex->lock(); + for (delete_list_t::iterator iter = mDeleteList.begin(); + iter != mDeleteList.end(); ) + { + delete_list_t::iterator curiter = iter++; + LLWorkerClass* worker = *curiter; + if (worker->deleteOK()) + { + if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED)) + { + worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED); + delete_list.push_back(worker); + mDeleteList.erase(curiter); + } + else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED)) + { + abort_list.push_back(worker); + } + } + } + mDeleteMutex->unlock(); + // abort and delete after releasing mutex + for (LLWorkerClass* worker : abort_list) + { + worker->abortWork(false); + } + for (LLWorkerClass* worker : delete_list) + { + if (worker->mRequestHandle) + { + // Finished but not completed + completeRequest(worker->mRequestHandle); + worker->mRequestHandle = LLWorkerThread::nullHandle(); + worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); + } + delete worker; + } + // delete and aborted entries mean there's still work to do + res += delete_list.size() + abort_list.size(); + return res; +} + +//---------------------------------------------------------------------------- + +LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param) +{ + handle_t handle = generateHandle(); + + WorkRequest* req = new WorkRequest(handle, workerclass, param); + + bool res = addRequest(req); + if (!res) + { + LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass) +{ + mDeleteMutex->lock(); + mDeleteList.push_back(workerclass); + mDeleteMutex->unlock(); +} + +//============================================================================ +// Runs on its OWN thread + +LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) : + LLQueuedThread::QueuedRequest(handle), + mWorkerClass(workerclass), + mParam(param) +{ +} + +LLWorkerThread::WorkRequest::~WorkRequest() +{ +} + +// virtual (required for access by LLWorkerThread) +void LLWorkerThread::WorkRequest::deleteRequest() +{ + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +// virtual +bool LLWorkerThread::WorkRequest::processRequest() +{ + LL_PROFILE_ZONE_SCOPED; + LLWorkerClass* workerclass = getWorkerClass(); + workerclass->setWorking(true); + bool complete = workerclass->doWork(getParam()); + workerclass->setWorking(false); + return complete; +} + +// virtual +void LLWorkerThread::WorkRequest::finishRequest(bool completed) +{ + LL_PROFILE_ZONE_SCOPED; + LLWorkerClass* workerclass = getWorkerClass(); + workerclass->finishWork(getParam(), completed); + U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED); + workerclass->setFlags(flags); +} + +//============================================================================ +// LLWorkerClass:: operates in main thread + +LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name) + : mWorkerThread(workerthread), + mWorkerClassName(name), + mRequestHandle(LLWorkerThread::nullHandle()), + mMutex(), + mWorkFlags(0) +{ + if (!mWorkerThread) + { + LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL; + } +} + +LLWorkerClass::~LLWorkerClass() +{ + llassert_always(!(mWorkFlags & WCF_WORKING)); + llassert_always(mWorkFlags & WCF_DELETE_REQUESTED); + llassert_always(!mMutex.isLocked()); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); + if (!workreq) + { + LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL; + } + if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED && + workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE) + { + LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL; + } + } +} + +void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread) +{ + mMutex.lock(); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL; + } + mWorkerThread = workerthread; + mMutex.unlock(); +} + +//---------------------------------------------------------------------------- + +//virtual +void LLWorkerClass::finishWork(S32 param, bool success) +{ +} + +//virtual +bool LLWorkerClass::deleteOK() +{ + return true; // default always OK +} + +//---------------------------------------------------------------------------- + +// Called from worker thread +void LLWorkerClass::setWorking(bool working) +{ + mMutex.lock(); + if (working) + { + llassert_always(!(mWorkFlags & WCF_WORKING)); + setFlags(WCF_WORKING); + } + else + { + llassert_always((mWorkFlags & WCF_WORKING)); + clearFlags(WCF_WORKING); + } + mMutex.unlock(); +} + +//---------------------------------------------------------------------------- + +bool LLWorkerClass::yield() +{ + LLThread::yield(); + mWorkerThread->checkPause(); + bool res; + mMutex.lock(); + res = (getFlags() & WCF_ABORT_REQUESTED) != 0; + mMutex.unlock(); + return res; +} + +//---------------------------------------------------------------------------- + +// calls startWork, adds doWork() to queue +void LLWorkerClass::addWork(S32 param) +{ + mMutex.lock(); + llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK))); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL; + } +#if _DEBUG +// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL; +#endif + startWork(param); + clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED); + setFlags(WCF_HAVE_WORK); + mRequestHandle = mWorkerThread->addWorkRequest(this, param); + mMutex.unlock(); +} + +void LLWorkerClass::abortWork(bool autocomplete) +{ + mMutex.lock(); +#if _DEBUG +// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle); +// if (workreq) +// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL; +#endif + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + mWorkerThread->abortRequest(mRequestHandle, autocomplete); + setFlags(WCF_ABORT_REQUESTED); + } + mMutex.unlock(); +} + +// if doWork is complete or aborted, call endWork() and return true +bool LLWorkerClass::checkWork(bool aborting) +{ + LLMutexLock lock(&mMutex); + bool complete = false, abort = false; + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); + if(!workreq) + { + if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running + { + mRequestHandle = LLWorkerThread::nullHandle(); + clearFlags(WCF_HAVE_WORK); + } + else + { + llassert_always(workreq); + } + return true ; + } + + LLQueuedThread::status_t status = workreq->getStatus(); + if (status == LLWorkerThread::STATUS_ABORTED) + { + complete = true; + abort = true; + } + else if (status == LLWorkerThread::STATUS_COMPLETE) + { + complete = true; + } + else + { + llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT)); + } + if (complete) + { + llassert_always(!(getFlags(WCF_WORKING))); + endWork(workreq->getParam(), abort); + mWorkerThread->completeRequest(mRequestHandle); + mRequestHandle = LLWorkerThread::nullHandle(); + clearFlags(WCF_HAVE_WORK); + } + } + else + { + complete = true; + } + return complete; +} + +void LLWorkerClass::scheduleDelete() +{ + bool do_delete = false; + mMutex.lock(); + if (!(getFlags(WCF_DELETE_REQUESTED))) + { + setFlags(WCF_DELETE_REQUESTED); + do_delete = true; + } + mMutex.unlock(); + if (do_delete) + { + mWorkerThread->deleteWorker(this); + } +} + +//============================================================================ + diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 803fff78d9..2a68584ab3 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -1,201 +1,201 @@ -/** - * @file llworkerthread.h - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLWORKERTHREAD_H -#define LL_LLWORKERTHREAD_H - -#include -#include -#include -#include -#include - -#include "llqueuedthread.h" -#include "llatomic.h" -#include "llmutex.h" - -#define USE_FRAME_CALLBACK_MANAGER 0 - -//============================================================================ - -class LLWorkerClass; - -//============================================================================ -// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small -// It is assumed that LLWorkerThreads are rarely created/destroyed. - -class LL_COMMON_API LLWorkerThread : public LLQueuedThread -{ - friend class LLWorkerClass; -public: - class WorkRequest : public LLQueuedThread::QueuedRequest - { - protected: - virtual ~WorkRequest(); // use deleteRequest() - - public: - WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param); - - S32 getParam() - { - return mParam; - } - LLWorkerClass* getWorkerClass() - { - return mWorkerClass; - } - - /*virtual*/ bool processRequest(); - /*virtual*/ void finishRequest(bool completed); - /*virtual*/ void deleteRequest(); - - private: - LLWorkerClass* mWorkerClass; - S32 mParam; - }; - -protected: - void clearDeleteList() ; - -private: - typedef std::list delete_list_t; - delete_list_t mDeleteList; - LLMutex* mDeleteMutex; - -public: - LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false); - ~LLWorkerThread(); - - /*virtual*/ size_t update(F32 max_time_ms); - - handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param); - - S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug - -private: - void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion - -}; - -//============================================================================ - -// This is a base class which any class with worker functions should derive from. -// Example Usage: -// LLMyWorkerClass* foo = new LLMyWorkerClass(); -// foo->fetchData(); // calls addWork() -// while(1) // main loop -// { -// if (foo->hasData()) // calls checkWork() -// foo->processData(); -// } -// -// WorkerClasses only have one set of work functions. If they need to do multiple -// background tasks, use 'param' to switch amnong them. -// Only one background task can be active at a time (per instance). -// i.e. don't call addWork() if haveWork() returns true - -class LL_COMMON_API LLWorkerClass -{ - friend class LLWorkerThread; - friend class LLWorkerThread::WorkRequest; - -public: - typedef LLWorkerThread::handle_t handle_t; - enum FLAGS - { - WCF_HAVE_WORK = 0x01, - WCF_WORKING = 0x02, - WCF_WORK_FINISHED = 0x10, - WCF_WORK_ABORTED = 0x20, - WCF_DELETE_REQUESTED = 0x40, - WCF_ABORT_REQUESTED = 0x80 - }; - -public: - LLWorkerClass(LLWorkerThread* workerthread, const std::string& name); - virtual ~LLWorkerClass(); - - // pure virtual, called from WORKER THREAD, returns true if done - virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest() - // virtual, called from finishRequest() after completed or aborted - virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) - // virtual, returns true if safe to delete the worker - virtual bool deleteOK(); // called from update() (WORK THREAD) - - // schedlueDelete(): schedules deletion once aborted or completed - void scheduleDelete(); - - bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted - bool isWorking() { return getFlags(WCF_WORKING); } - bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } - - const std::string& getName() const { return mWorkerClassName; } - -protected: - // called from WORKER THREAD - void setWorking(bool working); - - // Call from doWork only to avoid eating up cpu time. - // Returns true if work has been aborted - // yields the current thread and calls mWorkerThread->checkPause() - bool yield(); - - void setWorkerThread(LLWorkerThread* workerthread); - - // addWork(): calls startWork, adds doWork() to queue - void addWork(S32 param); - - // abortWork(): requests that work be aborted - void abortWork(bool autocomplete); - - // checkWork(): if doWork is complete or aborted, call endWork() and return true - bool checkWork(bool aborting = false); - -private: - void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; } - void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } - U32 getFlags() { return mWorkFlags; } -public: - bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; } - -private: - // pure virtuals - virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD) - virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD) - -protected: - LLWorkerThread* mWorkerThread; - std::string mWorkerClassName; - handle_t mRequestHandle; - -private: - LLMutex mMutex; - LLAtomicU32 mWorkFlags; -}; - -//============================================================================ - - -#endif // LL_LLWORKERTHREAD_H +/** + * @file llworkerthread.h + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLWORKERTHREAD_H +#define LL_LLWORKERTHREAD_H + +#include +#include +#include +#include +#include + +#include "llqueuedthread.h" +#include "llatomic.h" +#include "llmutex.h" + +#define USE_FRAME_CALLBACK_MANAGER 0 + +//============================================================================ + +class LLWorkerClass; + +//============================================================================ +// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small +// It is assumed that LLWorkerThreads are rarely created/destroyed. + +class LL_COMMON_API LLWorkerThread : public LLQueuedThread +{ + friend class LLWorkerClass; +public: + class WorkRequest : public LLQueuedThread::QueuedRequest + { + protected: + virtual ~WorkRequest(); // use deleteRequest() + + public: + WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param); + + S32 getParam() + { + return mParam; + } + LLWorkerClass* getWorkerClass() + { + return mWorkerClass; + } + + /*virtual*/ bool processRequest(); + /*virtual*/ void finishRequest(bool completed); + /*virtual*/ void deleteRequest(); + + private: + LLWorkerClass* mWorkerClass; + S32 mParam; + }; + +protected: + void clearDeleteList() ; + +private: + typedef std::list delete_list_t; + delete_list_t mDeleteList; + LLMutex* mDeleteMutex; + +public: + LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false); + ~LLWorkerThread(); + + /*virtual*/ size_t update(F32 max_time_ms); + + handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param); + + S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug + +private: + void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion + +}; + +//============================================================================ + +// This is a base class which any class with worker functions should derive from. +// Example Usage: +// LLMyWorkerClass* foo = new LLMyWorkerClass(); +// foo->fetchData(); // calls addWork() +// while(1) // main loop +// { +// if (foo->hasData()) // calls checkWork() +// foo->processData(); +// } +// +// WorkerClasses only have one set of work functions. If they need to do multiple +// background tasks, use 'param' to switch amnong them. +// Only one background task can be active at a time (per instance). +// i.e. don't call addWork() if haveWork() returns true + +class LL_COMMON_API LLWorkerClass +{ + friend class LLWorkerThread; + friend class LLWorkerThread::WorkRequest; + +public: + typedef LLWorkerThread::handle_t handle_t; + enum FLAGS + { + WCF_HAVE_WORK = 0x01, + WCF_WORKING = 0x02, + WCF_WORK_FINISHED = 0x10, + WCF_WORK_ABORTED = 0x20, + WCF_DELETE_REQUESTED = 0x40, + WCF_ABORT_REQUESTED = 0x80 + }; + +public: + LLWorkerClass(LLWorkerThread* workerthread, const std::string& name); + virtual ~LLWorkerClass(); + + // pure virtual, called from WORKER THREAD, returns true if done + virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest() + // virtual, called from finishRequest() after completed or aborted + virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) + // virtual, returns true if safe to delete the worker + virtual bool deleteOK(); // called from update() (WORK THREAD) + + // schedlueDelete(): schedules deletion once aborted or completed + void scheduleDelete(); + + bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted + bool isWorking() { return getFlags(WCF_WORKING); } + bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } + + const std::string& getName() const { return mWorkerClassName; } + +protected: + // called from WORKER THREAD + void setWorking(bool working); + + // Call from doWork only to avoid eating up cpu time. + // Returns true if work has been aborted + // yields the current thread and calls mWorkerThread->checkPause() + bool yield(); + + void setWorkerThread(LLWorkerThread* workerthread); + + // addWork(): calls startWork, adds doWork() to queue + void addWork(S32 param); + + // abortWork(): requests that work be aborted + void abortWork(bool autocomplete); + + // checkWork(): if doWork is complete or aborted, call endWork() and return true + bool checkWork(bool aborting = false); + +private: + void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; } + void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } + U32 getFlags() { return mWorkFlags; } +public: + bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; } + +private: + // pure virtuals + virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD) + virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD) + +protected: + LLWorkerThread* mWorkerThread; + std::string mWorkerClassName; + handle_t mRequestHandle; + +private: + LLMutex mMutex; + LLAtomicU32 mWorkFlags; +}; + +//============================================================================ + + +#endif // LL_LLWORKERTHREAD_H diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 89ef5b3450..b18712b8e9 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -1,871 +1,871 @@ -/** - * @file llstring_test.cpp - * @author Adroit, Steve Linden, Tofu Linden - * @date 2006-12-24 - * @brief Test cases of llstring.cpp - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include -#include "../llstring.h" -#include "StringVec.h" // must come BEFORE lltut.h -#include "../test/lltut.h" - -using boost::assign::list_of; - -namespace tut -{ - struct string_index - { - }; - typedef test_group string_index_t; - typedef string_index_t::object string_index_object_t; - tut::string_index_t tut_string_index("LLString"); - - template<> template<> - void string_index_object_t::test<1>() - { - std::string llstr1; - ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty()); - - std::string llstr2("Hello"); - ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty()); - - std::string llstr3(llstr2); - ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty()); - - std::string str("Hello World"); - std::string llstr4(str, 6); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty()); - - std::string llstr5(str, str.size()); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty()); - - std::string llstr6(5, 'A'); - ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty()); - - std::string llstr7("Hello World", 5); - ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty()); - - std::string llstr8("Hello World", 6, 5); - ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty()); - - std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end - ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty()); - } - - template<> template<> - void string_index_object_t::test<3>() - { - std::string str("Len=5"); - ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true && - LLStringUtil::isValidIndex(str, 5) == true && - LLStringUtil::isValidIndex(str, 6) == false); - - std::string str1; - ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false); - } - - template<> template<> - void string_index_object_t::test<4>() - { - std::string str_val(" Testing the extra whitespaces "); - LLStringUtil::trimHead(str_val); - ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces "); - - std::string str_val1("\n\t\r\n Testing the extra whitespaces "); - LLStringUtil::trimHead(str_val1); - ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces "); - } - - template<> template<> - void string_index_object_t::test<5>() - { - std::string str_val(" Testing the extra whitespaces "); - LLStringUtil::trimTail(str_val); - ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces"); - - std::string str_val1("\n Testing the extra whitespaces \n\t\r\n "); - LLStringUtil::trimTail(str_val1); - ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces"); - } - - - template<> template<> - void string_index_object_t::test<6>() - { - std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t "); - LLStringUtil::trim(str_val); - ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces"); - } - - template<> template<> - void string_index_object_t::test<7>() - { - std::string str("Second LindenLabs"); - LLStringUtil::truncate(str, 6); - ensure_equals("1: truncate", str, "Second"); - - // further truncate more than the length - LLStringUtil::truncate(str, 0); - ensure_equals("2: truncate", str, ""); - } - - template<> template<> - void string_index_object_t::test<8>() - { - std::string str_val("SecondLife Source"); - LLStringUtil::toUpper(str_val); - ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE"); - } - - template<> template<> - void string_index_object_t::test<9>() - { - std::string str_val("SecondLife Source"); - LLStringUtil::toLower(str_val); - ensure_equals("toLower failed", str_val, "secondlife source"); - } - - template<> template<> - void string_index_object_t::test<10>() - { - std::string str_val("Second"); - ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true); - ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false); - std::string str_val2(""); - ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false); - } - - template<> template<> - void string_index_object_t::test<11>() - { - std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n"); - std::string orig_str_val(str_val); - LLStringUtil::addCRLF(str_val); - ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); - LLStringUtil::removeCRLF(str_val); - ensure_equals("removeCRLF failed", str_val, orig_str_val); - } - - template<> template<> - void string_index_object_t::test<12>() - { - std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t"); - std::string orig_str_val(str_val); - LLStringUtil::replaceTabsWithSpaces(str_val, 1); - ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. "); - LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. "); - - str_val = "\t\t\t\t"; - LLStringUtil::replaceTabsWithSpaces(str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, ""); - } - - template<> template<> - void string_index_object_t::test<13>() - { - std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX."); - LLStringUtil::replaceNonstandardASCII(str_val, 'X'); - ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX."); - } - - template<> template<> - void string_index_object_t::test<14>() - { - std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); - LLStringUtil::replaceChar(str_val, 'A', 'X'); - ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); - std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); - } - - template<> template<> - void string_index_object_t::test<15>() - { - std::string str_val("Hello.\n\r\t"); - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true); - - str_val = "ABC "; - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false); - } - - template<> template<> - void string_index_object_t::test<16>() - { - std::string str_val("Hello.\n\r\t Again!"); - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable failed", str_val, "Hello. Again!"); - - str_val = "\r\n\t\t"; - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable resulting in empty string failed", str_val, ""); - - str_val = ""; - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, ""); - } - - template<> template<> - void string_index_object_t::test<17>() - { - bool value; - std::string str_val("1"); - ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "T"; - ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "t"; - ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "TRUE"; - ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "True"; - ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "true"; - ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value); - - str_val = "0"; - ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "F"; - ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "f"; - ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "FALSE"; - ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "False"; - ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "false"; - ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - - str_val = "Tblah"; - ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<18>() - { - U8 value; - std::string str_val("255"); - ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255); - - str_val = "0"; - ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0); - - str_val = "-1"; - ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); - - str_val = "256"; // bigger than MAX_U8 - ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<19>() - { - S8 value; - std::string str_val("127"); - ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127); - - str_val = "0"; - ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0); - - str_val = "-128"; - ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128); - - str_val = "128"; // bigger than MAX_S8 - ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); - - str_val = "-129"; - ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<20>() - { - S16 value; - std::string str_val("32767"); - ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767); - - str_val = "0"; - ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0); - - str_val = "-32768"; - ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768); - - str_val = "32768"; - ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); - - str_val = "-32769"; - ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<21>() - { - U16 value; - std::string str_val("65535"); //0xFFFF - ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535); - - str_val = "0"; - ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0); - - str_val = "-1"; - ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); - - str_val = "65536"; - ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<22>() - { - U32 value; - std::string str_val("4294967295"); //0xFFFFFFFF - ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL); - - str_val = "0"; - ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0); - - str_val = "4294967296"; - ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<23>() - { - S32 value; - std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647); - - str_val = "0"; - ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0); - - // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC - S32 min_val = -2147483647 - 1; - str_val = "-2147483648"; - ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val); - - str_val = "2147483648"; - ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<24>() - { - F32 value; - std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); - - str_val = "0"; - ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); - - /* Need to find max/min F32 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); - - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ - } - - template<> template<> - void string_index_object_t::test<25>() - { - F64 value; - std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF - ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); - - str_val = "0"; - ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); - - /* Need to find max/min F64 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); - - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ - } - - template<> template<> - void string_index_object_t::test<26>() - { - const char* str1 = NULL; - const char* str2 = NULL; - - ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); - str2 = "A"; - ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0); - ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0); - - str1 = "A is smaller than B"; - str2 = "B is greater than A"; - ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0); - - str2 = "A is smaller than B"; - ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); - } - - template<> template<> - void string_index_object_t::test<27>() - { - const char* str1 = NULL; - const char* str2 = NULL; - - ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); - str2 = "A"; - ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0); - ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0); - - str1 = "A is equal to a"; - str2 = "a is EQUAL to A"; - ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); - } - - template<> template<> - void string_index_object_t::test<28>() - { - std::string lhs_str("PROgraM12files"); - std::string rhs_str("PROgram12Files"); - ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); - ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true); - - lhs_str = "PROgram12Files"; - rhs_str = "PROgram12Files"; - ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); - ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); - - lhs_str = "PROgram12Files"; - rhs_str = "PROgRAM12FILES"; - ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); - ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); - } - - template<> template<> - void string_index_object_t::test<29>() - { - char str1[] = "First String..."; - char str2[100]; - - LLStringUtil::copy(str2, str1, 100); - ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0); - LLStringUtil::copy(str2, str1, sizeof("First")); - ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0); - } - - template<> template<> - void string_index_object_t::test<30>() - { - std::string str1 = "This is the sentence..."; - std::string str2 = "This is the "; - std::string str3 = "first "; - std::string str4 = "This is the first sentence..."; - std::string str5 = "This is the sentence...first "; - std::string dest; - - dest = str1; - LLStringUtil::copyInto(dest, str3, str2.length()); - ensure("LLStringUtil::copyInto insert failed", dest == str4); - - dest = str1; - LLStringUtil::copyInto(dest, str3, dest.length()); - ensure("LLStringUtil::copyInto append failed", dest == str5); - } - - template<> template<> - void string_index_object_t::test<31>() - { - std::string stripped; - - // Plain US ASCII text, including spaces and punctuation, - // should not be altered. - std::string simple_text = "Hello, world!"; - stripped = LLStringFn::strip_invalid_xml(simple_text); - ensure("Simple text passed unchanged", stripped == simple_text); - - // Control characters should be removed - // except for 0x09, 0x0a, 0x0d - std::string control_chars; - for (char c = 0x01; c < 0x20; c++) - { - control_chars.push_back(c); - } - std::string allowed_control_chars; - allowed_control_chars.push_back( (char)0x09 ); - allowed_control_chars.push_back( (char)0x0a ); - allowed_control_chars.push_back( (char)0x0d ); - - stripped = LLStringFn::strip_invalid_xml(control_chars); - ensure("Only tab, LF, CR control characters allowed", - stripped == allowed_control_chars); - - // UTF-8 should be passed intact, including high byte - // characters. Try Francais (with C squiggle cedilla) - std::string french = "Fran"; - french.push_back( (char)0xC3 ); - french.push_back( (char)0xA7 ); - french += "ais"; - stripped = LLStringFn::strip_invalid_xml( french ); - ensure("UTF-8 high byte text is allowed", french == stripped ); - } - - template<> template<> - void string_index_object_t::test<32>() - { - // Test LLStringUtil::format() string interpolation - LLStringUtil::format_map_t fmt_map; - std::string s; - int subcount; - - fmt_map["[TRICK1]"] = "[A]"; - fmt_map["[A]"] = "a"; - fmt_map["[B]"] = "b"; - fmt_map["[AAA]"] = "aaa"; - fmt_map["[BBB]"] = "bbb"; - fmt_map["[TRICK2]"] = "[A]"; - fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!"; - fmt_map["[KEYLONGER]"] = "short"; - fmt_map["[KEYSHORTER]"] = "Am I not a long string?"; - fmt_map["?"] = "?"; - fmt_map["[DELETE]"] = ""; - fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either - - for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) - { - // Test when source string is entirely one key - std::string s1 = (std::string)iter->first; - std::string s2 = (std::string)iter->second; - subcount = LLStringUtil::format(s1, fmt_map); - ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2); - if (s1 == "?" || s1 == "[]") // no interp expected - { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount); - } - else - { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount); - } - } - - for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) - { - // Test when source string is one key, duplicated - std::string s1 = (std::string)iter->first; - std::string s2 = (std::string)iter->second; - s = s1 + s1 + s1 + s1; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2); - if (s1 == "?" || s1 == "[]") // no interp expected - { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount); - } - else - { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount); - } - } - - // Test when source string has no keys - std::string srcs = "!!!!!!!!!!!!!!!!"; - s = srcs; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: No key test result", s, srcs); - ensure_equals("LLStringUtil::format: No key test result count", 0, subcount); - - // Test when source string has no keys and is empty - std::string srcs3; - s = srcs3; - subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: No key test3 result", s.empty()); - ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount); - - // Test a substitution where a key is substituted with blankness - std::string srcs2 = "[DELETE]"; - s = srcs2; - subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: Delete key test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount); - - // Test an assorted substitution - std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]"; - s = srcs4; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??"); - ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount); - - // Test an assorted substitution - std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]"; - s = srcs5; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); - - // Test on nested brackets - std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]"; - s = srcs6; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); - - - // Test an assorted substitution - std::string srcs8 = "foo[DELETE]bar?"; - s = srcs8; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?"); - ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount); - } - - template<> template<> - void string_index_object_t::test<33>() - { - // Test LLStringUtil::format() string interpolation - LLStringUtil::format_map_t blank_fmt_map; - std::string s; - int subcount; - - // Test substituting out of a blank format_map - std::string srcs6 = "12345"; - s = srcs6; - subcount = LLStringUtil::format(s, blank_fmt_map); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount); - - // Test substituting a blank string out of a blank format_map - std::string srcs7; - s = srcs7; - subcount = LLStringUtil::format(s, blank_fmt_map); - ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount); - } - - template<> template<> - void string_index_object_t::test<34>() - { - // Test that incorrect LLStringUtil::format() use does not explode. - LLStringUtil::format_map_t nasty_fmt_map; - std::string s; - int subcount; - - nasty_fmt_map[""] = "never used"; // see, this is nasty. - - // Test substituting out of a nasty format_map - std::string srcs6 = "12345"; - s = srcs6; - subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount); - - // Test substituting a blank string out of a nasty format_map - std::string srcs7; - s = srcs7; - subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount); - } - - template<> template<> - void string_index_object_t::test<35>() - { - // Make sure startsWith works - std::string string("anybody in there?"); - std::string substr("anybody"); - ensure("startsWith works.", LLStringUtil::startsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<36>() - { - // Make sure startsWith correctly fails - std::string string("anybody in there?"); - std::string substr("there"); - ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<37>() - { - // startsWith fails on empty strings - std::string value("anybody in there?"); - std::string empty; - ensure("empty string.", !LLStringUtil::startsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::startsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::startsWith(empty, empty)); - } - - template<> template<> - void string_index_object_t::test<38>() - { - // Make sure endsWith works correctly - std::string string("anybody in there?"); - std::string substr("there?"); - ensure("endsWith works.", LLStringUtil::endsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<39>() - { - // Make sure endsWith correctly fails - std::string string("anybody in there?"); - std::string substr("anybody"); - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - substr = "there"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - substr = "ther?"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<40>() - { - // endsWith fails on empty strings - std::string value("anybody in there?"); - std::string empty; - ensure("empty string.", !LLStringUtil::endsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); - } - - template<> template<> - void string_index_object_t::test<41>() - { - set_test_name("getTokens(\"delims\")"); - ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); - ensure_equals("only delims", - LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); - ensure_equals("sequence of delims", - LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one")); - // nat considers this a dubious implementation side effect, but I'd - // hate to change it now... - ensure_equals("noncontiguous tokens", - LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one")); - ensure_equals("space-padded tokens", - LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two")); - ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one")); - } - - // Shorthand for verifying that getTokens() behaves the same when you - // don't pass a string of escape characters, when you pass an empty string - // (different overloads), and when you pass a string of characters that - // aren't actually present. - void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::string& quotes, - const std::vector& expect) - { - ensure_equals(desc + " - no esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), - expect); - ensure_equals(desc + " - empty esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), - expect); - ensure_equals(desc + " - unused esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), - expect); - } - - void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::vector& expect) - { - ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); - } - - template<> template<> - void string_index_object_t::test<42>() - { - set_test_name("getTokens(\"delims\", etc.)"); - // Signatures to test in this method: - // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) - // If you omit keep_delims, you get the older function (test above). - - // cases like the getTokens(string, delims) tests above - ensure_getTokens("empty string", "", " ", "", StringVec()); - ensure_getTokens("only delims", - " \r\n ", " \r\n", "", StringVec()); - ensure_getTokens("sequence of delims", - ",,, one ,,,", ", ", "", list_of("one")); - // Note contrast with the case in the previous method - ensure_getTokens("noncontiguous tokens", - ", ,, , one ,,,", ", ", "", list_of("one")); - ensure_getTokens("space-padded tokens", - ", one , two ,", ", ", "", - list_of("one")("two")); - ensure_getTokens("no delims", "one", ",", "", list_of("one")); - - // drop_delims vs. keep_delims - ensure_getTokens("arithmetic", - " ab+def / xx* yy ", " ", "+-*/", - list_of("ab")("+")("def")("/")("xx")("*")("yy")); - - // quotes - ensure_getTokens("no quotes", - "She said, \"Don't go.\"", " ", ",", "", - list_of("She")("said")(",")("\"Don't")("go.\"")); - ensure_getTokens("quotes", - "She said, \"Don't go.\"", " ", ",", "\"", - list_of("She")("said")(",")("Don't go.")); - ensure_getTokens("quotes and delims", - "run c:/'Documents and Settings'/someone", " ", "", "'", - list_of("run")("c:/Documents and Settings/someone")); - ensure_getTokens("unmatched quote", - "baby don't leave", " ", "", "'", - list_of("baby")("don't")("leave")); - ensure_getTokens("adjacent quoted", - "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", - list_of("abcdef \"ghijkl' mnopqr")); - ensure_getTokens("quoted empty string", - "--set SomeVar ''", " ", "", "'", - list_of("--set")("SomeVar")("")); - - // escapes - // Don't use backslash as an escape for these tests -- you'll go nuts - // between the C++ string scanner and getTokens() escapes. Test with - // something else! - ensure_equals("escaped delims", - LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), - list_of(" a")("-")("dog-gone phrase")); - ensure_equals("escaped quotes", - LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), - list_of("say:")("this isn't working.")); - ensure_equals("escaped escape", - LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), - list_of("want")("x^2")); - ensure_equals("escape at end", - LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), - list_of("it's up")("there^")); - } -} +/** + * @file llstring_test.cpp + * @author Adroit, Steve Linden, Tofu Linden + * @date 2006-12-24 + * @brief Test cases of llstring.cpp + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include +#include "../llstring.h" +#include "StringVec.h" // must come BEFORE lltut.h +#include "../test/lltut.h" + +using boost::assign::list_of; + +namespace tut +{ + struct string_index + { + }; + typedef test_group string_index_t; + typedef string_index_t::object string_index_object_t; + tut::string_index_t tut_string_index("LLString"); + + template<> template<> + void string_index_object_t::test<1>() + { + std::string llstr1; + ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty()); + + std::string llstr2("Hello"); + ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty()); + + std::string llstr3(llstr2); + ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty()); + + std::string str("Hello World"); + std::string llstr4(str, 6); + ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty()); + + std::string llstr5(str, str.size()); + ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty()); + + std::string llstr6(5, 'A'); + ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty()); + + std::string llstr7("Hello World", 5); + ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty()); + + std::string llstr8("Hello World", 6, 5); + ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty()); + + std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end + ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty()); + } + + template<> template<> + void string_index_object_t::test<3>() + { + std::string str("Len=5"); + ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true && + LLStringUtil::isValidIndex(str, 5) == true && + LLStringUtil::isValidIndex(str, 6) == false); + + std::string str1; + ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false); + } + + template<> template<> + void string_index_object_t::test<4>() + { + std::string str_val(" Testing the extra whitespaces "); + LLStringUtil::trimHead(str_val); + ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces "); + + std::string str_val1("\n\t\r\n Testing the extra whitespaces "); + LLStringUtil::trimHead(str_val1); + ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces "); + } + + template<> template<> + void string_index_object_t::test<5>() + { + std::string str_val(" Testing the extra whitespaces "); + LLStringUtil::trimTail(str_val); + ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces"); + + std::string str_val1("\n Testing the extra whitespaces \n\t\r\n "); + LLStringUtil::trimTail(str_val1); + ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces"); + } + + + template<> template<> + void string_index_object_t::test<6>() + { + std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t "); + LLStringUtil::trim(str_val); + ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces"); + } + + template<> template<> + void string_index_object_t::test<7>() + { + std::string str("Second LindenLabs"); + LLStringUtil::truncate(str, 6); + ensure_equals("1: truncate", str, "Second"); + + // further truncate more than the length + LLStringUtil::truncate(str, 0); + ensure_equals("2: truncate", str, ""); + } + + template<> template<> + void string_index_object_t::test<8>() + { + std::string str_val("SecondLife Source"); + LLStringUtil::toUpper(str_val); + ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE"); + } + + template<> template<> + void string_index_object_t::test<9>() + { + std::string str_val("SecondLife Source"); + LLStringUtil::toLower(str_val); + ensure_equals("toLower failed", str_val, "secondlife source"); + } + + template<> template<> + void string_index_object_t::test<10>() + { + std::string str_val("Second"); + ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true); + ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false); + std::string str_val2(""); + ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false); + } + + template<> template<> + void string_index_object_t::test<11>() + { + std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n"); + std::string orig_str_val(str_val); + LLStringUtil::addCRLF(str_val); + ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); + LLStringUtil::removeCRLF(str_val); + ensure_equals("removeCRLF failed", str_val, orig_str_val); + } + + template<> template<> + void string_index_object_t::test<12>() + { + std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t"); + std::string orig_str_val(str_val); + LLStringUtil::replaceTabsWithSpaces(str_val, 1); + ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. "); + LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0); + ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. "); + + str_val = "\t\t\t\t"; + LLStringUtil::replaceTabsWithSpaces(str_val, 0); + ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, ""); + } + + template<> template<> + void string_index_object_t::test<13>() + { + std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX."); + LLStringUtil::replaceNonstandardASCII(str_val, 'X'); + ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX."); + } + + template<> template<> + void string_index_object_t::test<14>() + { + std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); + LLStringUtil::replaceChar(str_val, 'A', 'X'); + ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); + std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); + } + + template<> template<> + void string_index_object_t::test<15>() + { + std::string str_val("Hello.\n\r\t"); + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true); + + str_val = "ABC "; + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false); + } + + template<> template<> + void string_index_object_t::test<16>() + { + std::string str_val("Hello.\n\r\t Again!"); + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable failed", str_val, "Hello. Again!"); + + str_val = "\r\n\t\t"; + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable resulting in empty string failed", str_val, ""); + + str_val = ""; + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, ""); + } + + template<> template<> + void string_index_object_t::test<17>() + { + bool value; + std::string str_val("1"); + ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "T"; + ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "t"; + ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "TRUE"; + ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "True"; + ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "true"; + ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value); + + str_val = "0"; + ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "F"; + ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "f"; + ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "FALSE"; + ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "False"; + ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "false"; + ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + + str_val = "Tblah"; + ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<18>() + { + U8 value; + std::string str_val("255"); + ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255); + + str_val = "0"; + ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0); + + str_val = "-1"; + ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + + str_val = "256"; // bigger than MAX_U8 + ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<19>() + { + S8 value; + std::string str_val("127"); + ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127); + + str_val = "0"; + ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0); + + str_val = "-128"; + ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128); + + str_val = "128"; // bigger than MAX_S8 + ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + + str_val = "-129"; + ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<20>() + { + S16 value; + std::string str_val("32767"); + ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767); + + str_val = "0"; + ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0); + + str_val = "-32768"; + ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768); + + str_val = "32768"; + ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + + str_val = "-32769"; + ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<21>() + { + U16 value; + std::string str_val("65535"); //0xFFFF + ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535); + + str_val = "0"; + ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0); + + str_val = "-1"; + ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + + str_val = "65536"; + ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<22>() + { + U32 value; + std::string str_val("4294967295"); //0xFFFFFFFF + ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL); + + str_val = "0"; + ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0); + + str_val = "4294967296"; + ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<23>() + { + S32 value; + std::string str_val("2147483647"); //0x7FFFFFFF + ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647); + + str_val = "0"; + ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0); + + // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC + S32 min_val = -2147483647 - 1; + str_val = "-2147483648"; + ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val); + + str_val = "2147483648"; + ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<24>() + { + F32 value; + std::string str_val("2147483647"); //0x7FFFFFFF + ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); + + str_val = "0"; + ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); + + /* Need to find max/min F32 values + str_val = "-2147483648"; + ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + + str_val = "2147483648"; + ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + */ + } + + template<> template<> + void string_index_object_t::test<25>() + { + F64 value; + std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF + ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); + + str_val = "0"; + ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); + + /* Need to find max/min F64 values + str_val = "-2147483648"; + ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + + str_val = "2147483648"; + ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + */ + } + + template<> template<> + void string_index_object_t::test<26>() + { + const char* str1 = NULL; + const char* str2 = NULL; + + ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + str2 = "A"; + ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0); + ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0); + + str1 = "A is smaller than B"; + str2 = "B is greater than A"; + ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0); + + str2 = "A is smaller than B"; + ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + } + + template<> template<> + void string_index_object_t::test<27>() + { + const char* str1 = NULL; + const char* str2 = NULL; + + ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + str2 = "A"; + ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0); + ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0); + + str1 = "A is equal to a"; + str2 = "a is EQUAL to A"; + ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + } + + template<> template<> + void string_index_object_t::test<28>() + { + std::string lhs_str("PROgraM12files"); + std::string rhs_str("PROgram12Files"); + ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); + ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true); + + lhs_str = "PROgram12Files"; + rhs_str = "PROgram12Files"; + ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); + ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + + lhs_str = "PROgram12Files"; + rhs_str = "PROgRAM12FILES"; + ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); + ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + } + + template<> template<> + void string_index_object_t::test<29>() + { + char str1[] = "First String..."; + char str2[100]; + + LLStringUtil::copy(str2, str1, 100); + ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0); + LLStringUtil::copy(str2, str1, sizeof("First")); + ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0); + } + + template<> template<> + void string_index_object_t::test<30>() + { + std::string str1 = "This is the sentence..."; + std::string str2 = "This is the "; + std::string str3 = "first "; + std::string str4 = "This is the first sentence..."; + std::string str5 = "This is the sentence...first "; + std::string dest; + + dest = str1; + LLStringUtil::copyInto(dest, str3, str2.length()); + ensure("LLStringUtil::copyInto insert failed", dest == str4); + + dest = str1; + LLStringUtil::copyInto(dest, str3, dest.length()); + ensure("LLStringUtil::copyInto append failed", dest == str5); + } + + template<> template<> + void string_index_object_t::test<31>() + { + std::string stripped; + + // Plain US ASCII text, including spaces and punctuation, + // should not be altered. + std::string simple_text = "Hello, world!"; + stripped = LLStringFn::strip_invalid_xml(simple_text); + ensure("Simple text passed unchanged", stripped == simple_text); + + // Control characters should be removed + // except for 0x09, 0x0a, 0x0d + std::string control_chars; + for (char c = 0x01; c < 0x20; c++) + { + control_chars.push_back(c); + } + std::string allowed_control_chars; + allowed_control_chars.push_back( (char)0x09 ); + allowed_control_chars.push_back( (char)0x0a ); + allowed_control_chars.push_back( (char)0x0d ); + + stripped = LLStringFn::strip_invalid_xml(control_chars); + ensure("Only tab, LF, CR control characters allowed", + stripped == allowed_control_chars); + + // UTF-8 should be passed intact, including high byte + // characters. Try Francais (with C squiggle cedilla) + std::string french = "Fran"; + french.push_back( (char)0xC3 ); + french.push_back( (char)0xA7 ); + french += "ais"; + stripped = LLStringFn::strip_invalid_xml( french ); + ensure("UTF-8 high byte text is allowed", french == stripped ); + } + + template<> template<> + void string_index_object_t::test<32>() + { + // Test LLStringUtil::format() string interpolation + LLStringUtil::format_map_t fmt_map; + std::string s; + int subcount; + + fmt_map["[TRICK1]"] = "[A]"; + fmt_map["[A]"] = "a"; + fmt_map["[B]"] = "b"; + fmt_map["[AAA]"] = "aaa"; + fmt_map["[BBB]"] = "bbb"; + fmt_map["[TRICK2]"] = "[A]"; + fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!"; + fmt_map["[KEYLONGER]"] = "short"; + fmt_map["[KEYSHORTER]"] = "Am I not a long string?"; + fmt_map["?"] = "?"; + fmt_map["[DELETE]"] = ""; + fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either + + for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) + { + // Test when source string is entirely one key + std::string s1 = (std::string)iter->first; + std::string s2 = (std::string)iter->second; + subcount = LLStringUtil::format(s1, fmt_map); + ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2); + if (s1 == "?" || s1 == "[]") // no interp expected + { + ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount); + } + else + { + ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount); + } + } + + for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) + { + // Test when source string is one key, duplicated + std::string s1 = (std::string)iter->first; + std::string s2 = (std::string)iter->second; + s = s1 + s1 + s1 + s1; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2); + if (s1 == "?" || s1 == "[]") // no interp expected + { + ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount); + } + else + { + ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount); + } + } + + // Test when source string has no keys + std::string srcs = "!!!!!!!!!!!!!!!!"; + s = srcs; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: No key test result", s, srcs); + ensure_equals("LLStringUtil::format: No key test result count", 0, subcount); + + // Test when source string has no keys and is empty + std::string srcs3; + s = srcs3; + subcount = LLStringUtil::format(s, fmt_map); + ensure("LLStringUtil::format: No key test3 result", s.empty()); + ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount); + + // Test a substitution where a key is substituted with blankness + std::string srcs2 = "[DELETE]"; + s = srcs2; + subcount = LLStringUtil::format(s, fmt_map); + ensure("LLStringUtil::format: Delete key test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount); + + // Test an assorted substitution + std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]"; + s = srcs4; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??"); + ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount); + + // Test an assorted substitution + std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]"; + s = srcs5; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]"); + ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + + // Test on nested brackets + std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]"; + s = srcs6; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); + ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + + + // Test an assorted substitution + std::string srcs8 = "foo[DELETE]bar?"; + s = srcs8; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?"); + ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount); + } + + template<> template<> + void string_index_object_t::test<33>() + { + // Test LLStringUtil::format() string interpolation + LLStringUtil::format_map_t blank_fmt_map; + std::string s; + int subcount; + + // Test substituting out of a blank format_map + std::string srcs6 = "12345"; + s = srcs6; + subcount = LLStringUtil::format(s, blank_fmt_map); + ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345"); + ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount); + + // Test substituting a blank string out of a blank format_map + std::string srcs7; + s = srcs7; + subcount = LLStringUtil::format(s, blank_fmt_map); + ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount); + } + + template<> template<> + void string_index_object_t::test<34>() + { + // Test that incorrect LLStringUtil::format() use does not explode. + LLStringUtil::format_map_t nasty_fmt_map; + std::string s; + int subcount; + + nasty_fmt_map[""] = "never used"; // see, this is nasty. + + // Test substituting out of a nasty format_map + std::string srcs6 = "12345"; + s = srcs6; + subcount = LLStringUtil::format(s, nasty_fmt_map); + ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345"); + ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount); + + // Test substituting a blank string out of a nasty format_map + std::string srcs7; + s = srcs7; + subcount = LLStringUtil::format(s, nasty_fmt_map); + ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount); + } + + template<> template<> + void string_index_object_t::test<35>() + { + // Make sure startsWith works + std::string string("anybody in there?"); + std::string substr("anybody"); + ensure("startsWith works.", LLStringUtil::startsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<36>() + { + // Make sure startsWith correctly fails + std::string string("anybody in there?"); + std::string substr("there"); + ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<37>() + { + // startsWith fails on empty strings + std::string value("anybody in there?"); + std::string empty; + ensure("empty string.", !LLStringUtil::startsWith(value, empty)); + ensure("empty substr.", !LLStringUtil::startsWith(empty, value)); + ensure("empty everything.", !LLStringUtil::startsWith(empty, empty)); + } + + template<> template<> + void string_index_object_t::test<38>() + { + // Make sure endsWith works correctly + std::string string("anybody in there?"); + std::string substr("there?"); + ensure("endsWith works.", LLStringUtil::endsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<39>() + { + // Make sure endsWith correctly fails + std::string string("anybody in there?"); + std::string substr("anybody"); + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + substr = "there"; + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + substr = "ther?"; + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<40>() + { + // endsWith fails on empty strings + std::string value("anybody in there?"); + std::string empty; + ensure("empty string.", !LLStringUtil::endsWith(value, empty)); + ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); + ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); + } + + template<> template<> + void string_index_object_t::test<41>() + { + set_test_name("getTokens(\"delims\")"); + ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); + ensure_equals("only delims", + LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); + ensure_equals("sequence of delims", + LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one")); + // nat considers this a dubious implementation side effect, but I'd + // hate to change it now... + ensure_equals("noncontiguous tokens", + LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one")); + ensure_equals("space-padded tokens", + LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two")); + ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one")); + } + + // Shorthand for verifying that getTokens() behaves the same when you + // don't pass a string of escape characters, when you pass an empty string + // (different overloads), and when you pass a string of characters that + // aren't actually present. + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::string& quotes, + const std::vector& expect) + { + ensure_equals(desc + " - no esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), + expect); + ensure_equals(desc + " - empty esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), + expect); + ensure_equals(desc + " - unused esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), + expect); + } + + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::vector& expect) + { + ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); + } + + template<> template<> + void string_index_object_t::test<42>() + { + set_test_name("getTokens(\"delims\", etc.)"); + // Signatures to test in this method: + // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) + // If you omit keep_delims, you get the older function (test above). + + // cases like the getTokens(string, delims) tests above + ensure_getTokens("empty string", "", " ", "", StringVec()); + ensure_getTokens("only delims", + " \r\n ", " \r\n", "", StringVec()); + ensure_getTokens("sequence of delims", + ",,, one ,,,", ", ", "", list_of("one")); + // Note contrast with the case in the previous method + ensure_getTokens("noncontiguous tokens", + ", ,, , one ,,,", ", ", "", list_of("one")); + ensure_getTokens("space-padded tokens", + ", one , two ,", ", ", "", + list_of("one")("two")); + ensure_getTokens("no delims", "one", ",", "", list_of("one")); + + // drop_delims vs. keep_delims + ensure_getTokens("arithmetic", + " ab+def / xx* yy ", " ", "+-*/", + list_of("ab")("+")("def")("/")("xx")("*")("yy")); + + // quotes + ensure_getTokens("no quotes", + "She said, \"Don't go.\"", " ", ",", "", + list_of("She")("said")(",")("\"Don't")("go.\"")); + ensure_getTokens("quotes", + "She said, \"Don't go.\"", " ", ",", "\"", + list_of("She")("said")(",")("Don't go.")); + ensure_getTokens("quotes and delims", + "run c:/'Documents and Settings'/someone", " ", "", "'", + list_of("run")("c:/Documents and Settings/someone")); + ensure_getTokens("unmatched quote", + "baby don't leave", " ", "", "'", + list_of("baby")("don't")("leave")); + ensure_getTokens("adjacent quoted", + "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", + list_of("abcdef \"ghijkl' mnopqr")); + ensure_getTokens("quoted empty string", + "--set SomeVar ''", " ", "", "'", + list_of("--set")("SomeVar")("")); + + // escapes + // Don't use backslash as an escape for these tests -- you'll go nuts + // between the C++ string scanner and getTokens() escapes. Test with + // something else! + ensure_equals("escaped delims", + LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), + list_of(" a")("-")("dog-gone phrase")); + ensure_equals("escaped quotes", + LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), + list_of("say:")("this isn't working.")); + ensure_equals("escaped escape", + LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), + list_of("want")("x^2")); + ensure_equals("escape at end", + LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), + list_of("it's up")("there^")); + } +} -- cgit v1.2.3 From 2696b3de088877c3a406e817ed232c252700a16c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 24 May 2024 03:25:28 +0200 Subject: Introduce LLWStringView to prevent unnecessary memory allocations --- indra/llcommon/llstring.cpp | 2 +- indra/llcommon/llstring.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index bbb6aa2c20..5df0f8702d 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -758,7 +758,7 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) } // Search for any emoji symbol, return true if found -bool wstring_has_emoji(const LLWString& wstr) +bool wstring_has_emoji(LLWStringView wstr) { for (const llwchar& wch : wstr) { diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 61d698687a..123f4184b5 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -448,6 +448,7 @@ template std::string LLStringUtilBase::sLocale; typedef LLStringUtilBase LLStringUtil; typedef LLStringUtilBase LLWStringUtil; typedef std::basic_string LLWString; +typedef std::basic_string_view LLWStringView; //@ Use this where we want to disallow input in the form of "foo" // This is used to catch places where english text is embedded in the code @@ -749,7 +750,7 @@ LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); -LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr); +LL_COMMON_API bool wstring_has_emoji(LLWStringView wstr); LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr); -- cgit v1.2.3 From 2f4120038429c6aff865f153f708ceefb60d67f4 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 28 May 2024 09:45:40 -0500 Subject: Remove tinygltf dependency from LL::GLTF (#1541) * #1535 Image loading/saving support in boost::json driven GLTF parser * #1536 GLB Support in boost::json drvien GLTF parser --- indra/llcommon/lluuid.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 68c4b05fdc..b178868591 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -37,8 +37,8 @@ class LLMutex; const S32 UUID_BYTES = 16; const S32 UUID_WORDS = 4; -const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below -const S32 UUID_STR_SIZE = 37; +const S32 UUID_STR_LENGTH = 37; // number of bytes needed to store a UUID as a string +const S32 UUID_STR_SIZE = 36; // .size() of a UUID in a std::string const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. struct uuid_time_t { -- cgit v1.2.3 From c082f6f67a6dcd3437d1f2413932fef8cdf7de07 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Thu, 30 May 2024 13:11:50 +0200 Subject: Re-enable compiler warning C4800 (performance warning) --- indra/llcommon/llpreprocessor.h | 1 - indra/llcommon/lluuid.h | 2 -- 2 files changed, 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index a54408a852..6b6bd063ab 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -139,7 +139,6 @@ #endif #pragma warning( disable : 4503 ) // 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation. -#pragma warning( disable : 4800 ) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) #pragma warning( disable : 4996 ) // warning: deprecated // Linker optimization with "extern template" generates these warnings diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 68c4b05fdc..b382d6b3f9 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -82,8 +82,6 @@ public: // to integers, among other things. Use isNull() or notNull(). // operator bool() const; - // JC: These must return real bool's (not BOOLs) or else use of the STL - // will generate bool-to-int performance warnings. bool operator==(const LLUUID &rhs) const; bool operator!=(const LLUUID &rhs) const; bool operator<(const LLUUID &rhs) const; -- cgit v1.2.3 From cb3bd8865aa0f9fb8a247ea595cf1973057ba91f Mon Sep 17 00:00:00 2001 From: Ansariel Date: Thu, 30 May 2024 15:41:36 +0200 Subject: Fix a bunch of uninitialized variable warnings that showed up in Visual Studio --- indra/llcommon/llfasttimer.h | 2 +- indra/llcommon/llfile.h | 4 ++-- indra/llcommon/llsingleton.h | 1 + indra/llcommon/lltraceaccumulators.h | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 17ad37b031..09fcf8a1af 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -215,7 +215,7 @@ private: private: U64 mStartTime; - BlockTimerStackRecord mParentTimerData; + BlockTimerStackRecord mParentTimerData{}; public: // statics diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 08a008c19a..2564671b13 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -97,7 +97,7 @@ public: // no copy LLUniqueFile(const LLUniqueFile&) = delete; // move construction - LLUniqueFile(LLUniqueFile&& other) + LLUniqueFile(LLUniqueFile&& other) noexcept { mFileHandle = other.mFileHandle; other.mFileHandle = nullptr; @@ -118,7 +118,7 @@ public: // copy assignment deleted LLUniqueFile& operator=(const LLUniqueFile&) = delete; // move assignment - LLUniqueFile& operator=(LLUniqueFile&& other) + LLUniqueFile& operator=(LLUniqueFile&& other) noexcept { close(); std::swap(mFileHandle, other.mFileHandle); diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 61f82a4c03..0a7086e819 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -528,6 +528,7 @@ public: classname(), " -- creating new instance"}); // fall through + [[fallthrough]]; case UNINITIALIZED: case QUEUED: // QUEUED means some secondary thread has already requested an diff --git a/indra/llcommon/lltraceaccumulators.h b/indra/llcommon/lltraceaccumulators.h index 692bbe5acf..ba7acf9547 100644 --- a/indra/llcommon/lltraceaccumulators.h +++ b/indra/llcommon/lltraceaccumulators.h @@ -522,9 +522,9 @@ namespace LLTrace struct BlockTimerStackRecord { - class BlockTimer* mActiveTimer; - class BlockTimerStatHandle* mTimeBlock; - U64 mChildTime; + class BlockTimer* mActiveTimer{ nullptr }; + class BlockTimerStatHandle* mTimeBlock{ nullptr }; + U64 mChildTime{ 0 }; }; struct AccumulatorBufferGroup : public LLRefCount -- cgit v1.2.3 From b42f9d836b4c0f7fbd4bdae1734021e2a09fdbe8 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 1 Jun 2024 15:49:26 +0200 Subject: Re-enable a lot of compiler warnings for MSVC and address the C4267 "possible loss of precision" warnings --- indra/llcommon/llapp.cpp | 12 ++++++------ indra/llcommon/llfile.cpp | 2 +- indra/llcommon/llindexedvector.h | 8 ++++---- indra/llcommon/llpreprocessor.h | 17 ----------------- indra/llcommon/llqueuedthread.cpp | 2 +- indra/llcommon/llsdserialize.cpp | 8 ++++---- indra/llcommon/llstl.h | 2 +- indra/llcommon/llstring.cpp | 12 ++++++------ indra/llcommon/tests/commonmisc_test.cpp | 6 +++--- indra/llcommon/tests/lleventdispatcher_test.cpp | 8 ++++---- indra/llcommon/tests/llsdserialize_test.cpp | 18 +++++++++--------- 11 files changed, 39 insertions(+), 56 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 1388e81656..99ca0f740a 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -199,9 +199,9 @@ bool LLApp::parseCommandOptions(int argc, char** argv) #if LL_WINDOWS //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; + size_t slen = value.length() - 1; + size_t start = 0; + size_t end = slen; if (argv[ii][start]=='"')start++; if (argv[ii][end]=='"')end--; if (start!=0 || end!=slen) @@ -264,9 +264,9 @@ bool LLApp::parseCommandOptions(int argc, wchar_t** wargv) #if LL_WINDOWS //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; + size_t slen = value.length() - 1; + size_t start = 0; + size_t end = slen; if (wargv[ii][start]=='"')start++; if (wargv[ii][end]=='"')end--; if (start!=0 || end!=slen) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 1877dd54ed..6fdf58a99b 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -345,7 +345,7 @@ const char *LLFile::tmpdir() sep = '\\'; std::vector utf16path(MAX_PATH + 1); - GetTempPathW(utf16path.size(), &utf16path[0]); + GetTempPathW(static_cast(utf16path.size()), &utf16path[0]); utf8path = ll_convert_wide_to_string(&utf16path[0]); #else sep = '/'; diff --git a/indra/llcommon/llindexedvector.h b/indra/llcommon/llindexedvector.h index de3ae0dcc4..0b2e9c76ca 100644 --- a/indra/llcommon/llindexedvector.h +++ b/indra/llcommon/llindexedvector.h @@ -47,7 +47,7 @@ public: typedef typename std::vector::size_type size_type; protected: std::vector mVector; - std::map mIndexMap; + std::map mIndexMap; public: LLIndexedVector() { mVector.reserve(BlockSize); } @@ -68,10 +68,10 @@ public: Type& operator[](const Key& k) { - typename std::map::const_iterator iter = mIndexMap.find(k); + typename std::map::const_iterator iter = mIndexMap.find(k); if (iter == mIndexMap.end()) { - U32 n = mVector.size(); + auto n = mVector.size(); mIndexMap[k] = n; mVector.push_back(Type()); llassert(mVector.size() == mIndexMap.size()); @@ -85,7 +85,7 @@ public: const_iterator find(const Key& k) const { - typename std::map::const_iterator iter = mIndexMap.find(k); + typename std::map::const_iterator iter = mIndexMap.find(k); if(iter == mIndexMap.end()) { return mVector.end(); diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 6b6bd063ab..0c5799ad96 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -122,23 +122,6 @@ // Deal with VC6 problems #if LL_MSVC -#pragma warning( 3 : 4701 ) // "local variable used without being initialized" Treat this as level 3, not level 4. -#pragma warning( 3 : 4702 ) // "unreachable code" Treat this as level 3, not level 4. -#pragma warning( 3 : 4189 ) // "local variable initialized but not referenced" Treat this as level 3, not level 4. -//#pragma warning( 3 : 4018 ) // "signed/unsigned mismatch" Treat this as level 3, not level 4. -#pragma warning( 3 : 4263 ) // 'function' : member function does not override any base class virtual member function -#pragma warning( 3 : 4264 ) // "'virtual_function' : no override available for virtual member function from base 'class'; function is hidden" -#pragma warning( 3 : 4265 ) // "class has virtual functions, but destructor is not virtual" -#pragma warning( 3 : 4266 ) // 'function' : no override available for virtual member function from base 'type'; function is hidden -#pragma warning (disable : 4180) // qualifier applied to function type has no meaning; ignored -//#pragma warning( disable : 4284 ) // silly MS warning deep inside their include file - -#if ADDRESS_SIZE == 64 -// That one is all over the place for x64 builds. -#pragma warning( disable : 4267 ) // 'var' : conversion from 'size_t' to 'type', possible loss of data) -#endif - -#pragma warning( disable : 4503 ) // 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation. #pragma warning( disable : 4996 ) // warning: deprecated // Linker optimization with "extern template" generates these warnings diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 39e8113587..7d77f6f6a9 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -210,7 +210,7 @@ void LLQueuedThread::waitOnPending() // MAIN thread void LLQueuedThread::printQueueStats() { - U32 size = mRequestQueue.size(); + auto size = mRequestQueue.size(); if (size > 0) { LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 92d9392477..15002580c9 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1546,7 +1546,7 @@ S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, case LLSD::TypeMap: { ostr.put('{'); - U32 size_nbo = htonl(data.size()); + U32 size_nbo = htonl(static_cast(data.size())); ostr.write((const char*)(&size_nbo), sizeof(U32)); LLSD::map_const_iterator iter = data.beginMap(); LLSD::map_const_iterator end = data.endMap(); @@ -1563,7 +1563,7 @@ S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, case LLSD::TypeArray: { ostr.put('['); - U32 size_nbo = htonl(data.size()); + U32 size_nbo = htonl(static_cast(data.size())); ostr.write((const char*)(&size_nbo), sizeof(U32)); LLSD::array_const_iterator iter = data.beginArray(); LLSD::array_const_iterator end = data.endArray(); @@ -1630,7 +1630,7 @@ S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, { ostr.put('b'); const std::vector& buffer = data.asBinary(); - U32 size_nbo = htonl(buffer.size()); + U32 size_nbo = htonl(static_cast(buffer.size())); ostr.write((const char*)(&size_nbo), sizeof(U32)); if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); break; @@ -1648,7 +1648,7 @@ void LLSDBinaryFormatter::formatString( const std::string& string, std::ostream& ostr) const { - U32 size_nbo = htonl(string.size()); + U32 size_nbo = htonl(static_cast(string.size())); ostr.write((const char*)(&size_nbo), sizeof(U32)); ostr.write(string.c_str(), string.size()); } diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 67b4c141af..1b52d94258 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -326,7 +326,7 @@ inline bool vector_replace_with_last(std::vector& invec, const T& val) template inline T* vector_append(std::vector& invec, S32 N) { - U32 sz = invec.size(); + auto sz = invec.size(); invec.resize(sz+N); return &(invec[sz]); } diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 5df0f8702d..514d73b24b 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -809,7 +809,7 @@ std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned code_page, 0, in, - len_in, + static_cast(len_in), NULL, 0, 0, @@ -824,7 +824,7 @@ std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned code_page, 0, in, - len_in, + static_cast(len_in), pout, len_out, 0, @@ -851,8 +851,8 @@ std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int std::vector w_out(len + 1); memset(&w_out[0], 0, w_out.size()); - int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, - &w_out[0], w_out.size() - 1); + int real_output_str_len = MultiByteToWideChar(code_page, 0, in, static_cast(len), + &w_out[0], static_cast(w_out.size() - 1)); //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. w_out[real_output_str_len] = 0; @@ -943,7 +943,7 @@ std::optional llstring_getoptenv(const std::string& key) auto wkey = ll_convert_string_to_wide(key); // Take a wild guess as to how big the buffer should be. std::vector buffer(1024); - auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], static_cast(buffer.size())); // If our initial guess was too short, n will indicate the size (in // wchar_t's) that buffer should have been, including the terminating nul. if (n > (buffer.size() - 1)) @@ -951,7 +951,7 @@ std::optional llstring_getoptenv(const std::string& key) // make it big enough buffer.resize(n); // and try again - n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], static_cast(buffer.size())); } // did that (ultimately) succeed? if (n) diff --git a/indra/llcommon/tests/commonmisc_test.cpp b/indra/llcommon/tests/commonmisc_test.cpp index 3deb864c0c..0057a1f639 100644 --- a/indra/llcommon/tests/commonmisc_test.cpp +++ b/indra/llcommon/tests/commonmisc_test.cpp @@ -67,7 +67,7 @@ namespace tut std::ostringstream resp; resp << "{'connect':true, 'position':[r128,r128,r128], 'look_at':[r0,r1,r0], 'agent_access':'M', 'region_x':i8192, 'region_y':i8192}"; std::string str = resp.str(); - LLMemoryStream mstr((U8*)str.c_str(), str.size()); + LLMemoryStream mstr((U8*)str.c_str(), static_cast(str.size())); LLSD response; S32 count = LLSDSerialize::fromNotation(response, mstr, str.size()); ensure("stream parsed", response.isDefined()); @@ -252,7 +252,7 @@ namespace tut resp << "{'label':'short binary test', 'singlebinary':b(1)\"A\", 'singlerawstring':s(1)\"A\", 'endoftest':'end' }"; std::string str = resp.str(); LLSD sd; - LLMemoryStream mstr((U8*)str.c_str(), str.size()); + LLMemoryStream mstr((U8*)str.c_str(), static_cast(str.size())); S32 count = LLSDSerialize::fromNotation(sd, mstr, str.size()); ensure_equals("parse count", count, 5); ensure("sd created", sd.isDefined()); @@ -456,7 +456,7 @@ namespace tut void mem_object::test<1>() { const char HELLO_WORLD[] = "hello world"; - LLMemoryStream mem((U8*)&HELLO_WORLD[0], strlen(HELLO_WORLD)); /* Flawfinder: ignore */ + LLMemoryStream mem((U8*)&HELLO_WORLD[0], static_cast(strlen(HELLO_WORLD))); /* Flawfinder: ignore */ std::string hello; std::string world; mem >> hello >> world; diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index a99acba848..44f772e322 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -491,7 +491,7 @@ namespace tut // Partial defaults arrays. for (LLSD::String a: ab) { - LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size())); + LLSD::Integer partition(static_cast(std::min(partial_offset, dft_array_full[a].size()))); dft_array_partial[a] = llsd_copy_array(dft_array_full[a].beginArray() + partition, dft_array_full[a].endArray()); @@ -508,7 +508,7 @@ namespace tut // (params, dft_array_full). Part of the point of using map-style // defaults is to allow any subset of the target function's // parameters to be optional, not just the rightmost. - for (LLSD::Integer ix = 0, ixend = params[a].size(); ix < ixend; ix += 2) + for (LLSD::Integer ix = 0, ixend = static_cast(params[a].size()); ix < ixend; ix += 2) { dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix]; } @@ -696,7 +696,7 @@ namespace tut LLSD zipmap(const LLSD& keys, const LLSD& values) { LLSD map; - for (LLSD::Integer i = 0, iend = keys.size(); i < iend; ++i) + for (LLSD::Integer i = 0, iend = static_cast(keys.size()); i < iend; ++i) { // Have to select asString() since you can index an LLSD // object with either String or Integer. @@ -955,7 +955,7 @@ namespace tut allreq[a] = zipmap(params[a], LLSD::emptyArray()); // Same for leftreq, save that we use the subset of the params not // supplied by dft_array_partial[a]. - LLSD::Integer partition(params[a].size() - dft_array_partial[a].size()); + LLSD::Integer partition(static_cast(params[a].size() - dft_array_partial[a].size())); leftreq[a] = zipmap(llsd_copy_array(params[a].beginArray(), params[a].beginArray() + partition), LLSD::emptyArray()); diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 56fdc51e82..fb2af1d2db 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -701,7 +701,7 @@ namespace tut "cam1.23" "
", v, - v.size() + 1); + static_cast(v.size()) + 1); } template<> template<> @@ -721,7 +721,7 @@ namespace tut "cam1.23" "
", v, - v.size() + 1); + static_cast(v.size()) + 1); v.clear(); v["amy"] = 23; @@ -734,7 +734,7 @@ namespace tut "cam1.23" "
", v, - v.size() + 1); + static_cast(v.size()) + 1); v.clear(); v["amy"] = 23; @@ -751,7 +751,7 @@ namespace tut "cam1.23" "
", v, - v.size() + 1); + static_cast(v.size()) + 1); v.clear(); v[0] = 23; @@ -766,7 +766,7 @@ namespace tut "1.23" "", v, - v.size() + 1); + static_cast(v.size()) + 1); v.clear(); v[0] = 23; @@ -782,7 +782,7 @@ namespace tut "1.23" "", v, - v.size() + 1); + static_cast(v.size()) + 1); } template<> template<> @@ -1405,13 +1405,13 @@ namespace tut uint32_t size = htonl(1); memcpy(&vec[1], &size, sizeof(uint32_t)); vec.push_back('k'); - int key_size_loc = vec.size(); + auto key_size_loc = vec.size(); size = htonl(1); // 1 too short vec.resize(vec.size() + 4); memcpy(&vec[key_size_loc], &size, sizeof(uint32_t)); vec.push_back('a'); vec.push_back('m'); vec.push_back('y'); vec.push_back('i'); - int integer_loc = vec.size(); + auto integer_loc = vec.size(); vec.resize(vec.size() + 4); uint32_t val_int = htonl(23); memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); @@ -1473,7 +1473,7 @@ namespace tut memcpy(&vec[1], &size, sizeof(uint32_t)); vec.push_back('"'); vec.push_back('a'); vec.push_back('m'); vec.push_back('y'); vec.push_back('"'); vec.push_back('i'); - int integer_loc = vec.size(); + auto integer_loc = vec.size(); vec.resize(vec.size() + 4); uint32_t val_int = htonl(23); memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t)); -- cgit v1.2.3 From 24586f810eb7ef8048a55687333d51c53aa2bed8 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 5 Jun 2024 15:14:13 -0500 Subject: #1527 Improve performance on Apple silicon (#1632) --- indra/llcommon/llprofiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index af5e5777bf..722d9afca2 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -162,7 +162,7 @@ extern thread_local bool gProfilerEnabled; #define LL_LABEL_OBJECT_GL(type, name, length, label) -#if LL_PROFILER_CONFIGURATION > 1 +#if !LL_DARWIN && LL_PROFILER_CONFIGURATION > 1 #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) #define LL_PROFILE_FREE(ptr) TracyFree(ptr) #else -- cgit v1.2.3 From 3aaab6ae65fb7f3fb56c8a418624391d1394b998 Mon Sep 17 00:00:00 2001 From: Brad Linden <46733234+brad-linden@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:29:54 -0700 Subject: Ignore SIGHUP from SLPlugin processes. (#1627) Under rosetta2, terminating SLPlugin child processes that would normally send SIGCHILD seem to be now sending SIGHUP. we should not terminate the viewer in this case. --- indra/llcommon/llapp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 1388e81656..64497df875 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -586,9 +586,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) switch (signum) { case SIGCHLD: + case SIGHUP: if (LLApp::sLogInSignal) { - LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL; + LL_INFOS() << "Signal handler - Got SIGCHLD or SIGHUP from " << info->si_pid << LL_ENDL; } return; @@ -603,11 +604,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) raise(signum); return; case SIGINT: - case SIGHUP: case SIGTERM: if (LLApp::sLogInSignal) { - LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL; + LL_WARNS() << "Signal handler - Got SIGINT, or TERM, exiting gracefully" << LL_ENDL; } // Graceful exit // Just set our state to quitting, not error -- cgit v1.2.3 From d74173861d4de79c3ec92bbebf14edbc77c86a82 Mon Sep 17 00:00:00 2001 From: Brad Linden <46733234+brad-linden@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:49:02 -0700 Subject: Attempt to fix intermittent failuresin threadsafeschedule_test (#1623) --- indra/llcommon/tests/threadsafeschedule_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp index f2f17dd2e6..5e5d6e1259 100644 --- a/indra/llcommon/tests/threadsafeschedule_test.cpp +++ b/indra/llcommon/tests/threadsafeschedule_test.cpp @@ -47,11 +47,11 @@ namespace tut // the timestamp for each one -- but since we're passing explicit // timestamps, make the queue reorder them. auto now{ Queue::Clock::now() }; - queue.push(Queue::TimeTuple(now + 200ms, "ghi")); + queue.push(Queue::TimeTuple(now + 200ms, "ghi"s)); // Given the various push() overloads, you have to match the type // exactly: conversions are ambiguous. - queue.push("abc"s); - queue.push(now + 100ms, "def"); + queue.push(now, "abc"s); + queue.push(now + 100ms, "def"s); queue.close(); auto entry = queue.pop(); ensure_equals("failed to pop first", std::get<0>(entry), "abc"s); -- cgit v1.2.3 From c0fad3028fd55c2067ce6a0ae4382cffe1014284 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 10 Jun 2024 16:42:43 +0200 Subject: Re-enable compiler warnings C4018, C4100, C4231 and C4506 --- indra/llcommon/llalignedarray.h | 4 ++-- indra/llcommon/llleap.cpp | 2 +- indra/llcommon/llpreprocessor.h | 6 ------ indra/llcommon/llsingleton.h | 4 ++++ indra/llcommon/llstreamqueue.h | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llalignedarray.h b/indra/llcommon/llalignedarray.h index 0ba8b34cb6..8248f82186 100644 --- a/indra/llcommon/llalignedarray.h +++ b/indra/llcommon/llalignedarray.h @@ -116,7 +116,7 @@ void LLAlignedArray::resize(U32 size) template T& LLAlignedArray::operator[](int idx) { - if(idx >= mElementCount || idx < 0) + if (idx < 0 || unsigned(idx) >= mElementCount) { LL_ERRS() << "Out of bounds LLAlignedArray, requested: " << (S32)idx << " size: " << mElementCount << LL_ENDL; } @@ -126,7 +126,7 @@ T& LLAlignedArray::operator[](int idx) template const T& LLAlignedArray::operator[](int idx) const { - if (idx >= mElementCount || idx < 0) + if (idx < 0 || unsigned(idx) >= mElementCount) { LL_ERRS() << "Out of bounds LLAlignedArray, requested: " << (S32)idx << " size: " << mElementCount << LL_ENDL; } diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index e93ba83434..662a2511cd 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -233,7 +233,7 @@ public: LL_DEBUGS("EventHost") << "Sending: " << static_cast(buffer.tellp()) << ':'; - std::string::size_type truncate(80); + llssize truncate(80); if (buffer.tellp() <= truncate) { LL_CONT << buffer.str(); diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 0c5799ad96..65ecd573e4 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -124,12 +124,7 @@ #if LL_MSVC #pragma warning( disable : 4996 ) // warning: deprecated -// Linker optimization with "extern template" generates these warnings -#pragma warning( disable : 4231 ) // nonstandard extension used : 'extern' before template explicit instantiation -#pragma warning( disable : 4506 ) // no definition for inline function - // level 4 warnings that we need to disable: -#pragma warning (disable : 4100) // unreferenced formal parameter #pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) #pragma warning (disable : 4244) // possible loss of data on conversions #pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template @@ -138,7 +133,6 @@ #pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class #pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class -#pragma warning (disable : 4018) // '<' : signed/unsigned mismatch #endif // LL_MSVC diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 0a7086e819..5952cbdd87 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -36,6 +36,10 @@ #include "llthread.h" // on_main_thread() #include "llmainthreadtask.h" +#ifdef LL_WINDOWS +#pragma warning( disable : 4506 ) // no definition for inline function +#endif + class LLSingletonBase: private boost::noncopyable { public: diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h index a09bf4cb4b..01689457dd 100644 --- a/indra/llcommon/llstreamqueue.h +++ b/indra/llcommon/llstreamqueue.h @@ -216,7 +216,7 @@ std::streamsize LLGenericStreamQueue::skip(std::streamsize n) { typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end()); std::streamsize toskip(n), skipped(0); - while (bli != blend && toskip >= bli->length()) + while (bli != blend && (size_t)toskip >= bli->length()) { std::streamsize chunk(bli->length()); typename BufferList::iterator zap(bli++); -- cgit v1.2.3 From 4b52dd754b41948efca0087ccac6d813f1fcce3f Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 10 Jun 2024 18:16:13 +0200 Subject: Fix incorrect use of VX/VY/VZ/VW indices when color components are accessed --- indra/llcommon/lldefs.h | 154 ++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 77 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index 0ba756d472..2fbb26dc1a 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -31,64 +31,64 @@ #include // Often used array indices -const U32 VX = 0; -const U32 VY = 1; -const U32 VZ = 2; -const U32 VW = 3; -const U32 VS = 3; - -const U32 VRED = 0; -const U32 VGREEN = 1; -const U32 VBLUE = 2; -const U32 VALPHA = 3; - -const U32 INVALID_DIRECTION = 0xFFFFFFFF; -const U32 EAST = 0; -const U32 NORTH = 1; -const U32 WEST = 2; -const U32 SOUTH = 3; - -const U32 NORTHEAST = 4; -const U32 NORTHWEST = 5; -const U32 SOUTHWEST = 6; -const U32 SOUTHEAST = 7; -const U32 MIDDLE = 8; - -const U8 EAST_MASK = 0x1< Date: Mon, 10 Jun 2024 20:42:42 +0300 Subject: Post-merge - trim trailing whitespace --- indra/llcommon/StackWalker.cpp | 66 +++++++++++++++---------------- indra/llcommon/is_approx_equal_fraction.h | 10 ++--- indra/llcommon/llmainthreadtask.h | 2 +- indra/llcommon/llsdjson.cpp | 10 ++--- indra/llcommon/llsdjson.h | 22 +++++------ indra/llcommon/llsingleton.h | 12 +++--- indra/llcommon/llthread.h | 30 +++++++------- indra/llcommon/threadpool.cpp | 2 +- 8 files changed, 77 insertions(+), 77 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index 2c1bc47d0e..e9ae1723fb 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -1,5 +1,5 @@ /********************************************************************** - * + * * StackWalker.cpp * http://stackwalker.codeplex.com/ * @@ -13,14 +13,14 @@ * http://www.codeproject.com/threads/StackWalker.asp * 2005-07-28 v2 - Changed the params of the constructor and ShowCallstack * (to simplify the usage) - * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL + * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL * (should also be enough) * - Changed to compile correctly with the PSDK of VC7.0 * (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined: * it uses LPSTR instead of LPCSTR as first paremeter) * - Added declarations to support VC5/6 without using 'dbghelp.h' - * - Added a 'pUserData' member to the ShowCallstack function and the - * PReadProcessMemoryRoutine declaration (to pass some user-defined data, + * - Added a 'pUserData' member to the ShowCallstack function and the + * PReadProcessMemoryRoutine declaration (to pass some user-defined data, * which can be used in the readMemoryFunction-callback) * 2005-08-02 v4 - OnSymInit now also outputs the OS-Version by default * - Added example for doing an exception-callstack-walking in main.cpp @@ -60,26 +60,26 @@ * Copyright (c) 2005-2013, Jochen Kalmbach * All rights reserved. * - * Redistribution and use in source and binary forms, with or without modification, + * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 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. - * Neither the name of Jochen Kalmbach 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 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. + * Neither the name of Jochen Kalmbach 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. * **********************************************************************/ @@ -234,7 +234,7 @@ DWORD64 // Some missing defines (for VC5/6): #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) -#endif +#endif // secure-CRT_functions are only available starting with VC8 @@ -396,7 +396,7 @@ public: m_szSymPath = _strdup(szSymPath); if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); - + DWORD symOptions = this->pSGO(); // SymGetOptions symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; @@ -512,11 +512,11 @@ struct IMAGEHLP_MODULE64_V2 { tSSO pSSO; // StackWalk64() - typedef BOOL (__stdcall *tSW)( - DWORD MachineType, + typedef BOOL (__stdcall *tSW)( + DWORD MachineType, HANDLE hProcess, - HANDLE hThread, - LPSTACKFRAME64 StackFrame, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, @@ -1012,7 +1012,7 @@ bool StackWalker::LoadModules() // The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction // This has to be done due to a problem with the "hProcess"-parameter in x64... -// Because this class is in no case multi-threading-enabled (because of the limitations +// Because this class is in no case multi-threading-enabled (because of the limitations // of dbghelp.dll) it is "safe" to use a static-variable static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; static LPVOID s_readMemoryFunction_UserData = NULL; @@ -1222,7 +1222,7 @@ bool StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con csEntry.symTypeString = NULL; break; } - + MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName); csEntry.baseOfImage = Module.BaseOfImage; MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName); @@ -1243,7 +1243,7 @@ bool StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con et = firstEntry; bLastEntryCalled = false; this->OnCallstackEntry(et, csEntry); - + if (s.AddrReturn.Offset == 0) { bLastEntryCalled = true; @@ -1358,7 +1358,7 @@ void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUser ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA(&ver) != FALSE) { - _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion); if (m_verbose) @@ -1372,7 +1372,7 @@ void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUser ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE) { - _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); if (m_verbose) diff --git a/indra/llcommon/is_approx_equal_fraction.h b/indra/llcommon/is_approx_equal_fraction.h index 732d168986..371a1307c1 100644 --- a/indra/llcommon/is_approx_equal_fraction.h +++ b/indra/llcommon/is_approx_equal_fraction.h @@ -5,25 +5,25 @@ * @brief lltut.h uses is_approx_equal_fraction(). Moved to this header * file in llcommon so we can use lltut.h for llcommon tests without * making llcommon depend on llmath. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 5fae0212c4..cec95b2356 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -4,7 +4,7 @@ * @date 2019-12-04 * @brief LLMainThreadTask dispatches work to the main thread. When invoked on * the main thread, it performs the work inline. - * + * * $LicenseInfo:firstyear=2019&license=viewerlgpl$ * Copyright (c) 2019, Linden Research, Inc. * $/LicenseInfo$ diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 2fe24693dd..e95d2e6c1c 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -1,25 +1,25 @@ -/** +/** * @file llsdjson.cpp * @brief LLSD flexible data system * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2015, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h index 7173e26046..415bbf4821 100644 --- a/indra/llcommon/llsdjson.h +++ b/indra/llcommon/llsdjson.h @@ -1,25 +1,25 @@ -/** +/** * @file llsdjson.cpp * @brief LLSD flexible data system * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2015, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -36,10 +36,10 @@ #include "llsd.h" #include -/// Convert a parsed JSON structure into LLSD maintaining member names and +/// Convert a parsed JSON structure into LLSD maintaining member names and /// array indexes. /// JSON/JavaScript types are converted as follows: -/// +/// /// JSON Type | LLSD Type /// --------------+-------------- /// null | undefined @@ -50,14 +50,14 @@ /// boolean | LLSD::Boolean /// array | LLSD::Array /// object | LLSD::Map -/// +/// /// For maps and arrays child entries will be converted and added to the structure. /// Order is preserved for an array but not for objects. LLSD LlsdFromJson(const boost::json::value &val); -/// Convert an LLSD object into Parsed JSON object maintaining member names and +/// Convert an LLSD object into Parsed JSON object maintaining member names and /// array indexs. -/// +/// /// Types are converted as follows: /// LLSD Type | JSON Type /// --------------+---------------- @@ -71,7 +71,7 @@ LLSD LlsdFromJson(const boost::json::value &val); /// TypeUUID | string /// TypeMap | object /// TypeArray | array -/// TypeBinary | unsupported +/// TypeBinary | unsupported boost::json::value LlsdToJson(const LLSD &val); #endif // LL_LLSDJSON_H diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 5952cbdd87..7c6be25309 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -1,24 +1,24 @@ -/** +/** * @file llsingleton.h * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -831,7 +831,7 @@ private: \ // Relatively unsafe singleton implementation that is much faster // and simpler than LLSingleton, but has no dependency tracking -// or inherent thread safety and requires manual invocation of +// or inherent thread safety and requires manual invocation of // createInstance before first use. template class LLSimpleton diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index cf2ae00cd7..4194e0014d 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -1,25 +1,25 @@ -/** +/** * @file llthread.h * @brief Base classes for thread, mutex and condition handling. * * $LicenseInfo:firstyear=2004&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010-2013, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -55,10 +55,10 @@ public: LLThread(const std::string& name, apr_pool_t *poolp = NULL); virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state. virtual void shutdown(); // stops the thread - + bool isQuitting() const { return (QUITTING == mStatus); } bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); } - bool isCrashed() const { return (CRASHED == mStatus); } + bool isCrashed() const { return (CRASHED == mStatus); } static id_t currentID(); // Return ID of current thread static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. @@ -90,11 +90,11 @@ public: // internal state used by LLMutex. You must call this once early // in the running thread to prevent collisions with the main thread. static void registerThreadID(); - + private: bool mPaused; std::thread::native_handle_type mNativeHandle; // for termination in case of issues - + // static function passed to APR thread creation routine void threadRun(); @@ -111,21 +111,21 @@ protected: //a local apr_pool for APRFile operations in this thread. If it exists, LLAPRFile::sAPRFilePoolp should not be used. //Note: this pool is used by APRFile ONLY, do NOT use it for any other purposes. // otherwise it will cause severe memory leaking!!! --bao - LLVolatileAPRPool *mLocalAPRFilePoolp ; + LLVolatileAPRPool *mLocalAPRFilePoolp ; void setQuitting(); - + // virtual function overridden by subclass -- this will be called when the thread runs - virtual void run(void) = 0; - + virtual void run(void) = 0; + // virtual predicate function -- returns true if the thread should wake up, false if it should sleep. virtual bool runCondition(void); // Lock/Unlock Run Condition -- use around modification of any variable used in runCondition() void lockData(); void unlockData(); - - // This is the predicate that decides whether the thread should sleep. + + // This is the predicate that decides whether the thread should sleep. // It should only be called with mDataLock locked, since the virtual runCondition() function may need to access // data structures that are thread-unsafe. bool shouldSleep(void) { return (mStatus == RUNNING) && (isPaused() || (!runCondition())); } diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index dbcfb775da..451e60c083 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2021-10-21 * @brief Implementation for threadpool. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ -- cgit v1.2.3 From 9f6b8484dfb7dfa981d8a8ac3693d3f68e32bc12 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 10 Jun 2024 23:43:50 +0200 Subject: Re-enable compiler warnings C4127, C4512 & C4706 Disable particular CRT and WinSock API warnings for functions Microsoft considers unsafe/deprecated --- indra/llcommon/llfile.cpp | 12 ++++++------ indra/llcommon/llfindlocale.cpp | 8 ++++++++ indra/llcommon/llpreprocessor.h | 14 +++++++------- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 6fdf58a99b..ddf239f306 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -27,6 +27,12 @@ * $/LicenseInfo$ */ +#include "linden_common.h" +#include "llfile.h" +#include "llstring.h" +#include "llerror.h" +#include "stringize.h" + #if LL_WINDOWS #include "llwin32headerslean.h" #include // Windows errno @@ -35,12 +41,6 @@ #include #endif -#include "linden_common.h" -#include "llfile.h" -#include "llstring.h" -#include "llerror.h" -#include "stringize.h" - using namespace std; static std::string empty; diff --git a/indra/llcommon/llfindlocale.cpp b/indra/llcommon/llfindlocale.cpp index e39812bfc4..ac52f90c9f 100644 --- a/indra/llcommon/llfindlocale.cpp +++ b/indra/llcommon/llfindlocale.cpp @@ -157,14 +157,22 @@ canonise_fl(FL_Locale *l) { if (l->lang && 0 == strcmp(l->lang, "en")) { if (l->country && 0 == strcmp(l->country, "UK")) { free((void*)l->country); +#ifdef LL_WINDOWS + l->country = _strdup("GB"); +#else l->country = strdup("GB"); +#endif } } /* ja_JA -> ja_JP */ if (l->lang && 0 == strcmp(l->lang, "ja")) { if (l->country && 0 == strcmp(l->country, "JA")) { free((void*)l->country); +#ifdef LL_WINDOWS + l->country = _strdup("JP"); +#else l->country = strdup("JP"); +#endif } } } diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 65ecd573e4..0248e8f8b9 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -120,20 +120,20 @@ #endif // LL_WINDOWS -// Deal with VC6 problems +// Deal with VC++ problems #if LL_MSVC -#pragma warning( disable : 4996 ) // warning: deprecated +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS // disable warnings for methods considered unsafe +#endif +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS // disable deprecated WinSock API warnings +#endif // level 4 warnings that we need to disable: -#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) #pragma warning (disable : 4244) // possible loss of data on conversions #pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template -#pragma warning (disable : 4512) // assignment operator could not be generated -#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) - #pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class #pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class - #endif // LL_MSVC #if LL_WINDOWS -- cgit v1.2.3 From 8444cd9562a6a7b755fcb075864e205122354192 Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Wed, 12 Jun 2024 13:51:21 -0700 Subject: Fix whitespace pre-commit hook failures --- indra/llcommon/llcoros.cpp | 14 +++++++------- indra/llcommon/llcoros.h | 14 +++++++------- indra/llcommon/llevents.cpp | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 27 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 23b30bcc57..1539b48bd3 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-06-03 * @brief Implementation for llcoros. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -61,14 +61,14 @@ #include #endif -// static +// static bool LLCoros::on_main_coro() { if (!LLCoros::instanceExists() || LLCoros::getName().empty()) { return true; } - + return false; } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 00650a2454..369d65407e 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2009-06-02 * @brief Manage running boost::coroutine instances - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -100,7 +100,7 @@ public: static bool on_main_coro(); // For debugging, return true if on the main thread and not in a coroutine - // Non-thread-safe code in the main loop should be protected by + // Non-thread-safe code in the main loop should be protected by // llassert(LLCoros::on_main_thread_main_coro()) static bool on_main_thread_main_coro(); @@ -168,7 +168,7 @@ public: * LLCoros::launch()). */ static std::string getName(); - + /** * rethrow() is called by the thread's main fiber to propagate an * exception from any coroutine into the main fiber, where it can engage diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 8006f9d059..3c6743eac9 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -3,25 +3,25 @@ * @author Nat Goodspeed * @date 2008-09-12 * @brief Implementation for llevents. - * + * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -423,8 +423,8 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL float nodePosition = 1.0; - // if the supplied name is empty we are not interested in the ordering mechanism - // and can bypass attempting to find the optimal location to insert the new + // if the supplied name is empty we are not interested in the ordering mechanism + // and can bypass attempting to find the optimal location to insert the new // listener. We'll just tack it on to the end. if (!name.empty()) // should be the same as testing against ANONYMOUS { @@ -569,12 +569,12 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL // Now that newNode has a value that places it appropriately in mSignal, // connect it. LLBoundListener bound = mSignal->connect(nodePosition, listener); - + if (!name.empty()) { // note that we are not tracking anonymous listeners here either. - // This means that it is the caller's responsibility to either assign - // to a TempBoundListerer (scoped_connection) or manually disconnect - // when done. + // This means that it is the caller's responsibility to either assign + // to a TempBoundListerer (scoped_connection) or manually disconnect + // when done. mConnections[name] = bound; } return bound; @@ -641,9 +641,9 @@ bool LLEventMailDrop::post(const LLSD& event) { // forward the call to our base class bool posted = LLEventStream::post(event); - + if (!posted) - { // if the event was not handled we will save it for later so that it can + { // if the event was not handled we will save it for later so that it can // be posted to any future listeners when they attach. mEventHistory.push_back(event); } -- cgit v1.2.3 From f4eae44067cfe63fbe5ddd219b1b2463e9de89ef Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 14 Jun 2024 13:47:19 -0500 Subject: Fix for warnings/errors after Visual Studio update (#1775) --- indra/llcommon/llmemory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index 2c3f66fab8..80cfe554c4 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -134,7 +134,7 @@ public: \ void ll_aligned_free_fallback( void* ptr ); //------------------------------------------------------------------------------------------------ #else - inline void* ll_aligned_malloc_fallback( size_t size, int align ) + inline void* ll_aligned_malloc_fallback( size_t size, size_t align ) { LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; #if defined(LL_WINDOWS) -- cgit v1.2.3 From e4fd1bd71ef31b34ea92e2b5b5be4e08aad42269 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 20 Jun 2024 23:29:38 +0300 Subject: viewer#1808 Use bugsplat's setAttribute for app state --- indra/llcommon/llsys.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 496ec0d869..cfb05873df 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -226,16 +226,8 @@ LLOSInfo::LLOSInfo() : if (mBuild >= 22000) { // At release Windows 11 version was 10.0.22000.194 - // Windows 10 version was 10.0.19043.1266 - // There is no warranty that Win10 build won't increase, - // so until better solution is found or Microsoft updates - // SDK with IsWindows11OrGreater(), indicate "10/11" - // - // Current alternatives: - // Query WMI's Win32_OperatingSystem for OS string. Slow - // and likely to return 'compatibility' string. - // Check presence of dlls/libs or may be their version. - mOSStringSimple = "Microsoft Windows 10/11 "; + // According to microsoft win 10 won't ever get that far. + mOSStringSimple = "Microsoft Windows 11 "; } } -- cgit v1.2.3 From 80ea30af1a8b38360f71c29cc45872c4399dab0d Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 21 Jun 2024 13:13:08 -0500 Subject: #1769 gltf optimization pass (#1816) #1814 and #1517 Fix mirror update rate and occlusion culling --- indra/llcommon/llprofilercategories.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h index 0de343d020..1c4f0f5624 100644 --- a/indra/llcommon/llprofilercategories.h +++ b/indra/llcommon/llprofilercategories.h @@ -67,6 +67,7 @@ #define LL_PROFILER_CATEGORY_ENABLE_VERTEX 1 #define LL_PROFILER_CATEGORY_ENABLE_VOLUME 1 #define LL_PROFILER_CATEGORY_ENABLE_WIN32 1 +#define LL_PROFILER_CATEGORY_ENABLE_GLTF 1 #define LL_PROFILER_CATEGORY_ENABLE_VOICE 1 #if LL_PROFILER_CATEGORY_ENABLE_APP @@ -277,12 +278,19 @@ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32 #endif +#if LL_PROFILER_CATEGORY_ENABLE_GLTF + #define LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF LL_PROFILE_ZONE_NAMED + #define LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF LL_PROFILE_ZONE_SCOPED +#else + #define LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF(name) + #define LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF +#endif #if LL_PROFILER_CATEGORY_ENABLE_VOICE -#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE LL_PROFILE_ZONE_NAMED -#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE LL_PROFILE_ZONE_SCOPED + #define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE LL_PROFILE_ZONE_NAMED + #define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE LL_PROFILE_ZONE_SCOPED #else -#define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE(name) -#define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE + #define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE(name) + #define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE #endif #endif // LL_PROFILER_CATEGORIES_H -- cgit v1.2.3 From c2fbd139c10408460d94a525b979420cdcfe021b Mon Sep 17 00:00:00 2001 From: Beq Date: Thu, 27 Jun 2024 13:21:35 +0100 Subject: realign system ram functions make the system ram function align across all supported platforms. Taken from https://github.com/FirestormViewer/phoenix-firestorm/commit/3b074ba4af5e303125db606dd69eb4282a91f957 + clean up FS specific comment markers and upstream code retention --- indra/llcommon/llmemory.cpp | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 4b7d60d654..b9f9fdae17 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -39,6 +39,7 @@ #elif LL_LINUX # include # include +# include #endif #include "llmemory.h" @@ -85,6 +86,7 @@ void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) void LLMemory::updateMemoryInfo() { LL_PROFILE_ZONE_SCOPED + U32Kilobytes avail_phys; #if LL_WINDOWS PROCESS_MEMORY_COUNTERS counters; @@ -95,22 +97,10 @@ void LLMemory::updateMemoryInfo() } sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); - sample(sAllocatedMem, sAllocatedMemInKB); sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); sample(sVirtualMem, sAllocatedPageSizeInKB); - - U32Kilobytes avail_phys, avail_virtual; + U32Kilobytes avail_virtual; LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; - sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); - - if(sMaxPhysicalMemInKB > sAllocatedMemInKB) - { - sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; - } - else - { - sAvailPhysicalMemInKB = U32Kilobytes(0); - } #elif defined(LL_DARWIN) task_vm_info info; @@ -147,21 +137,39 @@ void LLMemory::updateMemoryInfo() if (result == KERN_SUCCESS) { // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. - sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); - sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); - } + avail_phys = U32Bytes(vmstat.free_count * page_size); + sMaxHeapSizeInKB = LLMemoryInfo::getHardwareMemSize(); + } else { LL_WARNS() << "task_info failed" << LL_ENDL; } - +#elif defined(LL_LINUX) + // Use sysinfo() to get the total physical memory. + struct sysinfo info; + sysinfo(&info); + sMaxHeapSizeInKB = U32Kilobytes::convert((U64Bytes)info.totalram); // Total RAM in system + avail_phys = U32Kilobytes::convert((U64Bytes)info.freeram); // Total Free RAM in system + sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(LLMemory::getCurrentRSS())); // represents the RAM allocated by this process only (in line with the windows implementation) #else //not valid for other systems for now. + LL_WARNS() << "LLMemory::updateMemoryInfo() not implemented for this platform." << LL_ENDL; sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); sMaxPhysicalMemInKB = U64Bytes(U32_MAX); sAvailPhysicalMemInKB = U64Bytes(U32_MAX); #endif + sample(sAllocatedMem, sAllocatedMemInKB); + // sMaxPhysicalMem - max this process can use = the lesser of (what we already have + what's available) or MaxHeap + sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); + if(sMaxPhysicalMemInKB > sAllocatedMemInKB) + { + sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; + } + else + { + sAvailPhysicalMemInKB = U32Kilobytes(0); + } return ; } -- cgit v1.2.3 From 7322623a6fe0e5120c07b720259e3beb325b097e Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Fri, 28 Jun 2024 12:44:41 -0700 Subject: Fix pre-commit whitespace checks and merge PR secondlife/viewer#1874 --- indra/llcommon/llmemory.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index b9f9fdae17..104c40f0d7 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -86,7 +86,7 @@ void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) void LLMemory::updateMemoryInfo() { LL_PROFILE_ZONE_SCOPED - U32Kilobytes avail_phys; + U32Kilobytes avail_phys; #if LL_WINDOWS PROCESS_MEMORY_COUNTERS counters; @@ -99,7 +99,7 @@ void LLMemory::updateMemoryInfo() sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); sample(sVirtualMem, sAllocatedPageSizeInKB); - U32Kilobytes avail_virtual; + U32Kilobytes avail_virtual; LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; #elif defined(LL_DARWIN) @@ -160,7 +160,7 @@ void LLMemory::updateMemoryInfo() #endif sample(sAllocatedMem, sAllocatedMemInKB); // sMaxPhysicalMem - max this process can use = the lesser of (what we already have + what's available) or MaxHeap - sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); + sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); if(sMaxPhysicalMemInKB > sAllocatedMemInKB) { -- cgit v1.2.3 From 2ea5ac0c43e3e28d2b1774f5367d099271a1da32 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 1 Jul 2024 13:34:50 +0200 Subject: #1111 Remove xmlrpc-epi --- indra/llcommon/llfile.cpp | 18 ++++ indra/llcommon/llfile.h | 2 + indra/llcommon/llsd.cpp | 201 +++++++++++++++++++++++++++++++++++++++++++- indra/llcommon/llsd.h | 20 ++++- indra/llcommon/llstring.cpp | 69 +++++++++++++++ indra/llcommon/llstring.h | 14 +++ 6 files changed, 317 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index ddf239f306..9045324bf2 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -248,6 +248,24 @@ int LLFile::close(LLFILE * file) return ret_value; } +std::string LLFile::getContents(const std::string& filename) +{ + LLFILE* fp = fopen(filename, "rb"); /* Flawfinder: ignore */ + if (fp) + { + fseek(fp, 0, SEEK_END); + U32 length = ftell(fp); + fseek(fp, 0, SEEK_SET); + + std::vector buffer(length); + size_t nread = fread(buffer.data(), 1, length, fp); + fclose(fp); + + return std::string(buffer.data(), nread); + } + + return LLStringUtil::null; +} int LLFile::remove(const std::string& filename, int supress_error) { diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 2564671b13..74110343fc 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -67,6 +67,8 @@ public: static int close(LLFILE * file); + static std::string getContents(const std::string& filename); + // perms is a permissions mask like 0777 or 0700. In most cases it will // be overridden by the user's umask. It is ignored on Windows. // mkdir() considers "directory already exists" to be SUCCESS. diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 663ceac22b..b36ff7d263 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -30,6 +30,7 @@ #include "linden_common.h" #include "llsd.h" +#include "llbase64.h" #include "llerror.h" #include "../llmath/llmath.h" #include "llformat.h" @@ -142,6 +143,8 @@ public: virtual const String& asStringRef() const { static const std::string empty; return empty; } + virtual String asXMLRPCValue() const { return ""; } + virtual bool has(const String&) const { return false; } virtual LLSD get(const String&) const { return LLSD(); } virtual LLSD getKeys() const { return LLSD::emptyArray(); } @@ -222,6 +225,8 @@ namespace virtual LLSD::Integer asInteger() const { return mValue ? 1 : 0; } virtual LLSD::Real asReal() const { return mValue ? 1 : 0; } virtual LLSD::String asString() const; + + virtual LLSD::String asXMLRPCValue() const { return mValue ? "1" : "0"; } }; LLSD::String ImplBoolean::asString() const @@ -243,6 +248,8 @@ namespace virtual LLSD::Integer asInteger() const { return mValue; } virtual LLSD::Real asReal() const { return mValue; } virtual LLSD::String asString() const; + + virtual LLSD::String asXMLRPCValue() const { return "" + std::to_string(mValue) + ""; } }; LLSD::String ImplInteger::asString() const @@ -259,6 +266,8 @@ namespace virtual LLSD::Integer asInteger() const; virtual LLSD::Real asReal() const { return mValue; } virtual LLSD::String asString() const; + + virtual LLSD::String asXMLRPCValue() const { return "" + std::to_string(mValue) + ""; } }; LLSD::Boolean ImplReal::asBoolean() const @@ -286,9 +295,11 @@ namespace virtual LLSD::URI asURI() const { return LLURI(mValue); } virtual size_t size() const { return mValue.size(); } virtual const LLSD::String& asStringRef() const { return mValue; } + + virtual LLSD::String asXMLRPCValue() const { return "" + LLStringFn::xml_encode(mValue) + ""; } }; - LLSD::Integer ImplString::asInteger() const + LLSD::Integer ImplString::asInteger() const { // This must treat "1.23" not as an error, but as a number, which is // then truncated down to an integer. Hence, this code doesn't call @@ -298,7 +309,7 @@ namespace return (int)asReal(); } - LLSD::Real ImplString::asReal() const + LLSD::Real ImplString::asReal() const { F64 v = 0.0; std::istringstream i_stream(mValue); @@ -323,6 +334,8 @@ namespace virtual LLSD::String asString() const{ return mValue.asString(); } virtual LLSD::UUID asUUID() const { return mValue; } + + virtual LLSD::String asXMLRPCValue() const { return "" + mValue.asString() + ""; } }; @@ -344,6 +357,8 @@ namespace } virtual LLSD::String asString() const{ return mValue.asString(); } virtual LLSD::Date asDate() const { return mValue; } + + virtual LLSD::String asXMLRPCValue() const { return "" + mValue.toHTTPDateString("%FT%T") + ""; } }; @@ -355,6 +370,8 @@ namespace virtual LLSD::String asString() const{ return mValue.asString(); } virtual LLSD::URI asURI() const { return mValue; } + + virtual LLSD::String asXMLRPCValue() const { return "" + LLStringFn::xml_encode(mValue.asString()) + ""; } }; @@ -365,13 +382,15 @@ namespace ImplBinary(const LLSD::Binary& v) : Base(v) { } virtual const LLSD::Binary& asBinary() const{ return mValue; } + + virtual LLSD::String asXMLRPCValue() const { return "" + LLBase64::encode(mValue.data(), mValue.size()) + ""; } }; class ImplMap : public LLSD::Impl { private: - typedef std::map DataMap; + typedef std::map DataMap; DataMap mData; @@ -387,6 +406,19 @@ namespace virtual LLSD::Boolean asBoolean() const { return !mData.empty(); } + virtual LLSD::String asXMLRPCValue() const + { + std::ostringstream os; + os << ""; + for (const auto& it : mData) + { + os << "" << LLStringFn::xml_encode(it.first) << "" + << it.second.asXMLRPCValue() << ""; + } + os << ""; + return os.str(); + } + virtual bool has(const LLSD::String&) const; using LLSD::Impl::get; // Unhiding get(size_t) @@ -511,7 +543,7 @@ namespace class ImplArray : public LLSD::Impl { private: - typedef std::vector DataVector; + typedef std::vector DataVector; DataVector mData; @@ -527,6 +559,18 @@ namespace virtual LLSD::Boolean asBoolean() const { return !mData.empty(); } + virtual LLSD::String asXMLRPCValue() const + { + std::ostringstream os; + os << ""; + for (const auto& it : mData) + { + os << it.asXMLRPCValue(); + } + os << ""; + return os.str(); + } + using LLSD::Impl::get; // Unhiding get(LLSD::String) using LLSD::Impl::erase; // Unhiding erase(LLSD::String) using LLSD::Impl::ref; // Unhiding ref(LLSD::String) @@ -872,6 +916,155 @@ const LLSD::Binary& LLSD::asBinary() const { return safe(impl).asBinary(); } const LLSD::String& LLSD::asStringRef() const { return safe(impl).asStringRef(); } +LLSD::String LLSD::asXMLRPCValue() const { return "" + safe(impl).asXMLRPCValue() + ""; } + +static bool inline check(bool condition, const char* warning_message) +{ + if (!condition) + { + LL_WARNS() << warning_message << LL_ENDL; + } + + return condition; +} + +static bool parseXMLRPCArrayValue(LLSD& target, LLSD::TreeNode* node) +{ + LLSD::TreeNode* data = node->getFirstChild(); + if (!check(data, "No array inner XML element ( expected)") || + !check(data->hasName("data"), "Invalid array inner XML element ( expected)") || + !check(!data->getNextSibling(), "Multiple array inner XML elements (single expected)")) + return false; + + for (LLSD::TreeNode* item = data->getFirstChild(); item; item = item->getNextSibling()) + { + LLSD value; + if (!value.fromXMLRPCValue(item)) + return false; + + target.append(value); + } + + return true; +} + +static bool parseXMLRPCStructValue(LLSD& target, LLSD::TreeNode* node) +{ + for (LLSD::TreeNode* item = node->getFirstChild(); item; item = item->getNextSibling()) + { + if (!check(item->hasName("member"), "Invalid struct inner XML element ( expected)")) + return false; + + std::string name; + LLSD value; + for (LLSD::TreeNode* subitem = item->getFirstChild(); subitem; subitem = subitem->getNextSibling()) + { + if (subitem->hasName("name")) + { + name = LLStringFn::xml_decode(subitem->getTextContents()); + } + else if (!value.fromXMLRPCValue(subitem)) + { + return false; + } + } + if (!check(!name.empty(), "Empty struct member name")) + return false; + + target.insert(name, value); + } + + return true; +} + +bool LLSD::fromXMLRPCValue(TreeNode* node) +{ + clear(); + + llassert(node); + if (!node) + return false; + + if (!check(node->hasName("value"), "Invalid XML element ( expected)")) + return false; + + TreeNode* inner = node->getFirstChild(); + if (!inner) + { + check(false, "No inner XML element (value type expected)"); + // Value with no type qualifier is treated as string + assign(LLStringFn::xml_decode(node->getTextContents())); + return true; + } + + if (!check(!inner->getNextSibling(), "Multiple inner XML elements (single expected)")) + return false; + + if (inner->hasName("string")) + { + assign(LLStringFn::xml_decode(inner->getTextContents())); + return true; + } + + if (inner->hasName("int") || inner->hasName("i4")) + { + assign(std::stoi(inner->getTextContents())); + return true; + } + + if (inner->hasName("double")) + { + assign(std::stod(inner->getTextContents())); + return true; + } + + if (inner->hasName("boolean")) + { + assign(!!std::stoi(inner->getTextContents())); + return true; + } + + if (inner->hasName("dateTime.iso8601")) + { + assign(Date(inner->getTextContents())); + return true; + } + + if (inner->hasName("base64")) + { + std::string decoded = LLBase64::decodeAsString(inner->getTextContents()); + Binary binary(decoded.size()); + memcpy(binary.data(), decoded.data(), decoded.size()); + assign(binary); + return true; + } + + if (inner->hasName("array")) + { + if (!parseXMLRPCArrayValue(*this, inner)) + { + clear(); + return false; + } + return true; + } + + if (inner->hasName("struct")) + { + if (!parseXMLRPCStructValue(*this, inner)) + { + clear(); + return false; + } + return true; + } + + check(false, "Unknown inner XML element (known value type expected)"); + // Value with unknown type qualifier is treated as string + assign(LLStringFn::xml_decode(inner->getTextContents())); + return true; +} + // const char * helpers LLSD::LLSD(const char* v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } void LLSD::assign(const char* v) diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index a5e735b561..5532decfc3 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -259,10 +259,24 @@ public: UUID asUUID() const; Date asDate() const; URI asURI() const; - const Binary& asBinary() const; + const Binary& asBinary() const; // asStringRef on any non-string type will return a ref to an empty string. - const String& asStringRef() const; + const String& asStringRef() const; + + // Return "<((type))>((scalar value or recursive calls))" + // See http://xmlrpc.com/spec.md + String asXMLRPCValue() const; + + struct TreeNode + { + virtual bool hasName(const String& name) const = 0; + virtual String getTextContents() const = 0; + virtual TreeNode* getFirstChild() const = 0; + virtual TreeNode* getNextSibling() const = 0; + }; + + bool fromXMLRPCValue(TreeNode* node); operator Boolean() const { return asBoolean(); } operator Integer() const { return asInteger(); } @@ -275,7 +289,7 @@ public: // This is needed because most platforms do not automatically // convert the boolean negation as a bool in an if statement. - bool operator!() const {return !asBoolean();} + bool operator!() const { return !asBoolean(); } //@} /** @name Character Pointer Helpers diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 514d73b24b..6f3d193d6b 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1208,6 +1208,75 @@ namespace LLStringFn return output; } + using literals_t = std::map; + static const literals_t xml_elem_literals = + { + { '<', "<" }, + { '>', ">" }, + { '&', "&" } + }; + static const literals_t xml_attr_literals = + { + { '"', """ }, + { '\'', "'" } + }; + + static void literals_encode(std::string& text, const literals_t& literals) + { + for (const std::pair it : literals) + { + std::string::size_type pos = 0; + while ((pos = text.find(it.first, pos)) != std::string::npos) + { + text.replace(pos, 1, it.second); + pos += it.second.size(); + } + } + } + + static void literals_decode(std::string& text, const literals_t& literals) + { + for (const std::pair it : literals) + { + std::string::size_type pos = 0; + while ((pos = text.find(it.second, pos)) != std::string::npos) + { + text[pos++] = it.first; + text.erase(pos, it.second.size() - 1); + } + } + } + + /** + * @brief Replace all characters that are not allowed in XML 1.0 + * with corresponding literals: [ < > & ] => [ < > & ] + */ + std::string xml_encode(const std::string& input, bool for_attribute) + { + std::string result(input); + literals_encode(result, xml_elem_literals); + if (for_attribute) + { + literals_encode(result, xml_attr_literals); + } + return result; + } + + /** + * @brief Replace some of XML literals that are defined in XML 1.0 + * with corresponding characters: [ < > & ] => [ < > & ] + */ + std::string xml_decode(const std::string& input, bool for_attribute) + { + std::string result(input); + literals_decode(result, xml_elem_literals); + if (for_attribute) + { + literals_decode(result, xml_attr_literals); + } + return result; + } + /** * @brief Replace all control characters (c < 0x20) with replacement in * string. diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 123f4184b5..b69a068830 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -889,6 +889,20 @@ namespace LLStringFn LL_COMMON_API std::string strip_invalid_xml(const std::string& input); + /** + * @brief Replace all characters that are not allowed in XML 1.0 + * with corresponding literals: [ < > & ] => [ < > & ] + */ + LL_COMMON_API std::string xml_encode(const std::string& input, bool for_attribute = false); + + + /** + * @brief Replace some of XML literals that are defined in XML 1.0 + * with corresponding characters: [ < > & ] => [ < > & ] + */ + LL_COMMON_API std::string xml_decode(const std::string& input, bool for_attribute = false); + + /** * @brief Replace all control characters (0 <= c < 0x20) with replacement in * string. This is safe for utf-8 -- cgit v1.2.3 From b0e30477e93bb16b0cf8c7b64aaee35cedf85ca8 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Mon, 1 Jul 2024 22:25:56 -0400 Subject: Use heterogeneous comparison for string_view map finds in LLControl and convert controlExists to string_view --- indra/llcommon/llstl.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 1b52d94258..e3999cbdaa 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -226,11 +226,11 @@ void delete_and_clear_array(T*& ptr) // foo[2] = "hello"; // const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" // const char* baz = get_ptr_in_map(foo, 3); // baz == NULL -template -inline T* get_ptr_in_map(const std::map& inmap, const K& key) +template +inline typename T::mapped_type get_ptr_in_map(const T& inmap, typename T::key_type const& key) { // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map::const_iterator map_iter; + typedef T::const_iterator map_iter; map_iter iter = inmap.find(key); if(iter == inmap.end()) { -- cgit v1.2.3 From fad6a3753757778d4b50d46f44aabd0d3fa3e13b Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Mon, 1 Jul 2024 22:58:25 -0400 Subject: Fix test builds --- indra/llcommon/llstl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index e3999cbdaa..7d41c42ba7 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -230,7 +230,7 @@ template inline typename T::mapped_type get_ptr_in_map(const T& inmap, typename T::key_type const& key) { // Typedef here avoids warnings because of new c++ naming rules. - typedef T::const_iterator map_iter; + typedef typename T::const_iterator map_iter; map_iter iter = inmap.find(key); if(iter == inmap.end()) { -- cgit v1.2.3 From 59312bf209c5fcdb27c283caaa2ed36502ec7a33 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Wed, 3 Jul 2024 17:32:45 -0400 Subject: Introduce string_view to LLSD map functions to reduce string temporaries --- indra/llcommon/llsd.cpp | 55 ++++++++++++++++++++++++++++--------------------- indra/llcommon/llsd.h | 28 ++++++++++++------------- 2 files changed, 44 insertions(+), 39 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index b36ff7d263..a9cd8c597e 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -145,11 +145,11 @@ public: virtual String asXMLRPCValue() const { return ""; } - virtual bool has(const String&) const { return false; } - virtual LLSD get(const String&) const { return LLSD(); } + virtual bool has(std::string_view) const { return false; } + virtual LLSD get(std::string_view) const { return LLSD(); } virtual LLSD getKeys() const { return LLSD::emptyArray(); } virtual void erase(const String&) { } - virtual const LLSD& ref(const String&) const{ return undef(); } + virtual const LLSD& ref(std::string_view) const{ return undef(); } virtual size_t size() const { return 0; } virtual LLSD get(size_t) const { return LLSD(); } @@ -390,7 +390,7 @@ namespace class ImplMap : public LLSD::Impl { private: - typedef std::map DataMap; + typedef std::map> DataMap; DataMap mData; @@ -419,17 +419,17 @@ namespace return os.str(); } - virtual bool has(const LLSD::String&) const; + virtual bool has(std::string_view) const; using LLSD::Impl::get; // Unhiding get(size_t) using LLSD::Impl::erase; // Unhiding erase(size_t) using LLSD::Impl::ref; // Unhiding ref(size_t) - virtual LLSD get(const LLSD::String&) const; + virtual LLSD get(std::string_view) const; virtual LLSD getKeys() const; - void insert(const LLSD::String& k, const LLSD& v); + void insert(std::string_view k, const LLSD& v); virtual void erase(const LLSD::String&); - LLSD& ref(const LLSD::String&); - virtual const LLSD& ref(const LLSD::String&) const; + LLSD& ref(std::string_view); + virtual const LLSD& ref(std::string_view) const; virtual size_t size() const { return mData.size(); } @@ -457,14 +457,14 @@ namespace } } - bool ImplMap::has(const LLSD::String& k) const + bool ImplMap::has(const std::string_view k) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; DataMap::const_iterator i = mData.find(k); return i != mData.end(); } - LLSD ImplMap::get(const LLSD::String& k) const + LLSD ImplMap::get(const std::string_view k) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; DataMap::const_iterator i = mData.find(k); @@ -484,10 +484,10 @@ namespace return keys; } - void ImplMap::insert(const LLSD::String& k, const LLSD& v) + void ImplMap::insert(std::string_view k, const LLSD& v) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; - mData.insert(DataMap::value_type(k, v)); + mData.emplace(k, v); } void ImplMap::erase(const LLSD::String& k) @@ -496,15 +496,22 @@ namespace mData.erase(k); } - LLSD& ImplMap::ref(const LLSD::String& k) + LLSD& ImplMap::ref(std::string_view k) { - return mData[k]; + DataMap::iterator i = mData.lower_bound(k); + if (i == mData.end() || mData.key_comp()(k, i->first)) + { + + return mData.emplace_hint(i, std::make_pair(k, LLSD()))->second; + } + + return i->second; } - const LLSD& ImplMap::ref(const LLSD::String& k) const + const LLSD& ImplMap::ref(std::string_view k) const { DataMap::const_iterator i = mData.lower_bound(k); - if (i == mData.end() || mData.key_comp()(k, i->first)) + if (i == mData.end() || mData.key_comp()(k, i->first)) { return undef(); } @@ -691,7 +698,7 @@ namespace while (iter != endArray()) { // Add values for all items held in the array Impl::calcStats((*iter), type_counts, share_counts); - iter++; + ++iter; } // Add in the values for this array @@ -1081,24 +1088,24 @@ LLSD LLSD::emptyMap() return v; } -bool LLSD::has(const String& k) const { return safe(impl).has(k); } -LLSD LLSD::get(const String& k) const { return safe(impl).get(k); } +bool LLSD::has(const std::string_view k) const { return safe(impl).has(k); } +LLSD LLSD::get(const std::string_view k) const { return safe(impl).get(k); } LLSD LLSD::getKeys() const { return safe(impl).getKeys(); } -void LLSD::insert(const String& k, const LLSD& v) { makeMap(impl).insert(k, v); } +void LLSD::insert(std::string_view k, const LLSD& v) { makeMap(impl).insert(k, v); } -LLSD& LLSD::with(const String& k, const LLSD& v) +LLSD& LLSD::with(std::string_view k, const LLSD& v) { makeMap(impl).insert(k, v); return *this; } void LLSD::erase(const String& k) { makeMap(impl).erase(k); } -LLSD& LLSD::operator[](const String& k) +LLSD& LLSD::operator[](const std::string_view k) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; return makeMap(impl).ref(k); } -const LLSD& LLSD::operator[](const String& k) const +const LLSD& LLSD::operator[](const std::string_view k) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; return safe(impl).ref(k); diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index 5532decfc3..675cbb0edb 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -306,25 +306,23 @@ public: //@{ static LLSD emptyMap(); - bool has(const String&) const; - LLSD get(const String&) const; + bool has(const std::string_view) const; + LLSD get(const std::string_view) const; LLSD getKeys() const; // Return an LLSD array with keys as strings - void insert(const String&, const LLSD&); + void insert(std::string_view, const LLSD&); void erase(const String&); - LLSD& with(const String&, const LLSD&); + LLSD& with(std::string_view, const LLSD&); - LLSD& operator[](const String&); + LLSD& operator[](const std::string_view); LLSD& operator[](const char* c) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; - return (*this)[String(c)]; - } - const LLSD& operator[](const String&) const; - const LLSD& operator[](const char* c) const - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; - return (*this)[String(c)]; - } + { + return c ? (*this)[std::string_view(c)] : *this; + } + const LLSD& operator[](const std::string_view) const; + const LLSD& operator[](const char* c) const + { + return c ? (*this)[std::string_view(c)] : *this; + } //@} /** @name Array Values */ -- cgit v1.2.3 From 7df1edbde100d1c07bd4d2edcfa76ea7a870de42 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Thu, 4 Jul 2024 00:49:45 -0400 Subject: Introduce move assignment operators for various LLSD types --- indra/llcommon/llsd.cpp | 100 ++++++++++++++++++++++++++++++++++++++++++------ indra/llcommon/llsd.h | 15 ++++++++ 2 files changed, 104 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index a9cd8c597e..0576182c98 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -123,11 +123,17 @@ public: virtual void assign(Impl*& var, LLSD::Boolean); virtual void assign(Impl*& var, LLSD::Integer); virtual void assign(Impl*& var, LLSD::Real); + virtual void assign(Impl*& var, const char*); virtual void assign(Impl*& var, const LLSD::String&); virtual void assign(Impl*& var, const LLSD::UUID&); virtual void assign(Impl*& var, const LLSD::Date&); virtual void assign(Impl*& var, const LLSD::URI&); virtual void assign(Impl*& var, const LLSD::Binary&); + virtual void assign(Impl*& var, LLSD::String&&); + virtual void assign(Impl*& var, LLSD::UUID&&); + virtual void assign(Impl*& var, LLSD::Date&&); + virtual void assign(Impl*& var, LLSD::URI&&); + virtual void assign(Impl*& var, LLSD::Binary&&); ///< If the receiver is the right type and unshared, these are simple // data assignments, othewise the default implementation handless // constructing the proper Impl subclass @@ -185,7 +191,7 @@ namespace LLSDUnnamedNamespace namespace #endif { - template + template class ImplBase : public LLSD::Impl ///< This class handles most of the work for a subclass of Impl // for a given simple data type. Subclasses of this provide the @@ -198,6 +204,7 @@ namespace public: ImplBase(DataRef value) : mValue(value) { } + ImplBase(DataMove value) : mValue(std::move(value)) { } virtual LLSD::Type type() const { return T; } @@ -212,11 +219,21 @@ namespace mValue = value; } } + virtual void assign(LLSD::Impl*& var, DataMove value) { + if (shared()) + { + Impl::assign(var, std::move(value)); + } + else + { + mValue = std::move(value); + } + } }; class ImplBoolean - : public ImplBase + : public ImplBase { public: ImplBoolean(LLSD::Boolean v) : Base(v) { } @@ -239,7 +256,7 @@ namespace class ImplInteger - : public ImplBase + : public ImplBase { public: ImplInteger(LLSD::Integer v) : Base(v) { } @@ -257,7 +274,7 @@ namespace class ImplReal - : public ImplBase + : public ImplBase { public: ImplReal(LLSD::Real v) : Base(v) { } @@ -281,10 +298,11 @@ namespace class ImplString - : public ImplBase + : public ImplBase { public: ImplString(const LLSD::String& v) : Base(v) { } + ImplString(LLSD::String&& v) : Base(std::move(v)) {} virtual LLSD::Boolean asBoolean() const { return !mValue.empty(); } virtual LLSD::Integer asInteger() const; @@ -297,6 +315,19 @@ namespace virtual const LLSD::String& asStringRef() const { return mValue; } virtual LLSD::String asXMLRPCValue() const { return "" + LLStringFn::xml_encode(mValue) + ""; } + + using LLSD::Impl::assign; // Unhiding base class virtuals... + virtual void assign(LLSD::Impl*& var, const char* value) + { + if (shared()) + { + Impl::assign(var, value); + } + else + { + mValue = value; + } + } }; LLSD::Integer ImplString::asInteger() const @@ -327,10 +358,11 @@ namespace class ImplUUID - : public ImplBase + : public ImplBase { public: ImplUUID(const LLSD::UUID& v) : Base(v) { } + ImplUUID(LLSD::UUID&& v) : Base(std::move(v)) { } virtual LLSD::String asString() const{ return mValue.asString(); } virtual LLSD::UUID asUUID() const { return mValue; } @@ -340,13 +372,17 @@ namespace class ImplDate - : public ImplBase + : public ImplBase { public: ImplDate(const LLSD::Date& v) - : ImplBase(v) + : ImplBase(v) { } + ImplDate(LLSD::Date&& v) + : ImplBase(std::move(v)) + { } + virtual LLSD::Integer asInteger() const { return (LLSD::Integer)(mValue.secondsSinceEpoch()); @@ -363,10 +399,11 @@ namespace class ImplURI - : public ImplBase + : public ImplBase { public: ImplURI(const LLSD::URI& v) : Base(v) { } + ImplURI(LLSD::URI&& v) : Base(std::move(v)) { } virtual LLSD::String asString() const{ return mValue.asString(); } virtual LLSD::URI asURI() const { return mValue; } @@ -376,10 +413,11 @@ namespace class ImplBinary - : public ImplBase + : public ImplBase { public: ImplBinary(const LLSD::Binary& v) : Base(v) { } + ImplBinary(LLSD::Binary&& v) : Base(std::move(v)) { } virtual const LLSD::Binary& asBinary() const{ return mValue; } @@ -789,6 +827,11 @@ void LLSD::Impl::assign(Impl*& var, LLSD::Real v) reset(var, new ImplReal(v)); } +void LLSD::Impl::assign(Impl*& var, const char* v) +{ + reset(var, new ImplString(v)); +} + void LLSD::Impl::assign(Impl*& var, const LLSD::String& v) { reset(var, new ImplString(v)); @@ -814,6 +857,31 @@ void LLSD::Impl::assign(Impl*& var, const LLSD::Binary& v) reset(var, new ImplBinary(v)); } +void LLSD::Impl::assign(Impl*& var, LLSD::String&& v) +{ + reset(var, new ImplString(std::move(v))); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::UUID&& v) +{ + reset(var, new ImplUUID(std::move(v))); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::Date&& v) +{ + reset(var, new ImplDate(std::move(v))); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::URI&& v) +{ + reset(var, new ImplURI(std::move(v))); +} + +void LLSD::Impl::assign(Impl*& var, LLSD::Binary&& v) +{ + reset(var, new ImplBinary(std::move(v))); +} + const LLSD& LLSD::Impl::undef() { @@ -900,6 +968,11 @@ LLSD::LLSD(const String& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } LLSD::LLSD(const Date& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } LLSD::LLSD(const URI& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } LLSD::LLSD(const Binary& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } +LLSD::LLSD(UUID&& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(std::move(v)); } +LLSD::LLSD(String&& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(std::move(v)); } +LLSD::LLSD(Date&& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(std::move(v)); } +LLSD::LLSD(URI&& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(std::move(v)); } +LLSD::LLSD(Binary&& v) : impl(0) { ALLOC_LLSD_OBJECT; assign(std::move(v)); } // Scalar Assignment void LLSD::assign(Boolean v) { safe(impl).assign(impl, v); } @@ -910,6 +983,11 @@ void LLSD::assign(const UUID& v) { safe(impl).assign(impl, v); } void LLSD::assign(const Date& v) { safe(impl).assign(impl, v); } void LLSD::assign(const URI& v) { safe(impl).assign(impl, v); } void LLSD::assign(const Binary& v) { safe(impl).assign(impl, v); } +void LLSD::assign(String&& v) { safe(impl).assign(impl, std::move(v)); } +void LLSD::assign(UUID&& v) { safe(impl).assign(impl, std::move(v)); } +void LLSD::assign(Date&& v) { safe(impl).assign(impl, std::move(v)); } +void LLSD::assign(URI&& v) { safe(impl).assign(impl, std::move(v)); } +void LLSD::assign(Binary&& v) { safe(impl).assign(impl, std::move(v)); } // Scalar Accessors LLSD::Boolean LLSD::asBoolean() const { return safe(impl).asBoolean(); } @@ -1076,7 +1154,7 @@ bool LLSD::fromXMLRPCValue(TreeNode* node) LLSD::LLSD(const char* v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } void LLSD::assign(const char* v) { - if(v) assign(std::string(v)); + if(v) safe(impl).assign(impl, v); else assign(std::string()); } diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index 675cbb0edb..77f1606554 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -188,6 +188,11 @@ public: LLSD(const Date&); LLSD(const URI&); LLSD(const Binary&); + LLSD(String&&); + LLSD(UUID&&); + LLSD(Date&&); + LLSD(URI&&); + LLSD(Binary&&); //@} /** @name Convenience Constructors */ @@ -215,6 +220,11 @@ public: void assign(const Date&); void assign(const URI&); void assign(const Binary&); + void assign(String&&); + void assign(UUID&&); + void assign(Date&&); + void assign(URI&&); + void assign(Binary&&); LLSD& operator=(Boolean v) { assign(v); return *this; } LLSD& operator=(Integer v) { assign(v); return *this; } @@ -224,6 +234,11 @@ public: LLSD& operator=(const Date& v) { assign(v); return *this; } LLSD& operator=(const URI& v) { assign(v); return *this; } LLSD& operator=(const Binary& v) { assign(v); return *this; } + LLSD& operator=(String&& v) { assign(std::move(v)); return *this; } + LLSD& operator=(UUID&& v) { assign(std::move(v)); return *this; } + LLSD& operator=(Date&& v) { assign(std::move(v)); return *this; } + LLSD& operator=(URI&& v) { assign(std::move(v)); return *this; } + LLSD& operator=(Binary&& v) { assign(std::move(v)); return *this; } //@} /** -- cgit v1.2.3 From 6cf176900f0c89e20557742dbcd1174799440f8f Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Thu, 4 Jul 2024 05:02:46 -0400 Subject: Add move construction/assignment support for LLSD type --- indra/llcommon/llsd.cpp | 37 ++++++++++++++++++++++++++----------- indra/llcommon/llsd.h | 21 ++++++++++++++------- 2 files changed, 40 insertions(+), 18 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 0576182c98..2bbe06e72f 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -106,6 +106,9 @@ public: static void reset(Impl*& var, Impl* impl); ///< safely set var to refer to the new impl (possibly shared) + static void move(Impl*& var, Impl*& impl); + ///< safely move impl from one object to another + static Impl& safe( Impl*); static const Impl& safe(const Impl*); ///< since a NULL Impl* is used for undefined, this ensures there is @@ -232,7 +235,7 @@ namespace }; - class ImplBoolean + class ImplBoolean final : public ImplBase { public: @@ -255,7 +258,7 @@ namespace { return mValue ? "true" : ""; } - class ImplInteger + class ImplInteger final : public ImplBase { public: @@ -273,7 +276,7 @@ namespace { return llformat("%d", mValue); } - class ImplReal + class ImplReal final : public ImplBase { public: @@ -297,7 +300,7 @@ namespace { return llformat("%lg", mValue); } - class ImplString + class ImplString final : public ImplBase { public: @@ -357,7 +360,7 @@ namespace } - class ImplUUID + class ImplUUID final : public ImplBase { public: @@ -371,7 +374,7 @@ namespace }; - class ImplDate + class ImplDate final : public ImplBase { public: @@ -398,7 +401,7 @@ namespace }; - class ImplURI + class ImplURI final : public ImplBase { public: @@ -412,7 +415,7 @@ namespace }; - class ImplBinary + class ImplBinary final : public ImplBase { public: @@ -425,7 +428,7 @@ namespace }; - class ImplMap : public LLSD::Impl + class ImplMap final : public LLSD::Impl { private: typedef std::map> DataMap; @@ -539,7 +542,6 @@ namespace DataMap::iterator i = mData.lower_bound(k); if (i == mData.end() || mData.key_comp()(k, i->first)) { - return mData.emplace_hint(i, std::make_pair(k, LLSD()))->second; } @@ -577,7 +579,7 @@ namespace { //std::cout << " " << (*iter).first << ": " << (*iter).second << std::endl; Impl::calcStats((*iter).second, type_counts, share_counts); - iter++; + ++iter; } // Add in the values for this map @@ -774,6 +776,16 @@ void LLSD::Impl::reset(Impl*& var, Impl* impl) var = impl; } +void LLSD::Impl::move(Impl*& var, Impl*& impl) +{ + if (var && var->mUseCount != STATIC_USAGE_COUNT && --var->mUseCount == 0) + { + delete var; // destroy var if usage falls to 0 and not static + } + var = impl; // Steal impl to var without incrementing use since this is a move + impl = nullptr; // null out old-impl pointer +} + LLSD::Impl& LLSD::Impl::safe(Impl* impl) { static Impl theUndefined(STATIC_USAGE_COUNT); @@ -954,6 +966,9 @@ LLSD::~LLSD() { FREE_LLSD_OBJECT; Impl::reset(impl, 0) LLSD::LLSD(const LLSD& other) : impl(0) { ALLOC_LLSD_OBJECT; assign(other); } void LLSD::assign(const LLSD& other) { Impl::assign(impl, other.impl); } +LLSD::LLSD(LLSD&& other) noexcept : impl(nullptr) { ALLOC_LLSD_OBJECT; Impl::move(impl, other.impl); } +void LLSD::assign(LLSD&& other) { Impl::move(impl, other.impl); } +LLSD& LLSD::operator=(LLSD&& other) noexcept { Impl::move(impl, other.impl); return *this; } void LLSD::clear() { Impl::assignUndefined(impl); } diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index 77f1606554..781e8d58e9 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -161,6 +161,13 @@ public: //@} + /** @name Movable */ + //@{ + LLSD(LLSD&& other) noexcept; + void assign(LLSD&& other); + LLSD& operator=(LLSD&& other) noexcept; + //@} + void clear(); ///< resets to Undefined @@ -330,14 +337,14 @@ public: LLSD& operator[](const std::string_view); LLSD& operator[](const char* c) - { - return c ? (*this)[std::string_view(c)] : *this; - } + { + return c ? (*this)[std::string_view(c)] : *this; + } const LLSD& operator[](const std::string_view) const; - const LLSD& operator[](const char* c) const - { - return c ? (*this)[std::string_view(c)] : *this; - } + const LLSD& operator[](const char* c) const + { + return c ? (*this)[std::string_view(c)] : *this; + } //@} /** @name Array Values */ -- cgit v1.2.3 From f6d2536e4f2633757cce75f45a1ee9f6003be90a Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Thu, 4 Jul 2024 13:01:02 -0400 Subject: Make LLDate a trivial copyable/movable type --- indra/llcommon/lldate.cpp | 9 --------- indra/llcommon/lldate.h | 9 +++------ 2 files changed, 3 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp index c63c7012d1..b38864688d 100644 --- a/indra/llcommon/lldate.cpp +++ b/indra/llcommon/lldate.cpp @@ -41,20 +41,11 @@ #include "llstring.h" #include "llfasttimer.h" -static const F64 DATE_EPOCH = 0.0; - static const F64 LL_APR_USEC_PER_SEC = 1000000.0; // should be APR_USEC_PER_SEC, but that relies on INT64_C which // isn't defined in glib under our build set up for some reason -LLDate::LLDate() : mSecondsSinceEpoch(DATE_EPOCH) -{} - -LLDate::LLDate(const LLDate& date) : - mSecondsSinceEpoch(date.mSecondsSinceEpoch) -{} - LLDate::LLDate(F64SecondsImplicit seconds_since_epoch) : mSecondsSinceEpoch(seconds_since_epoch.value()) {} diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index 81f2dd0d1c..1a69a04232 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -43,16 +43,13 @@ */ class LL_COMMON_API LLDate { + static constexpr F64 DATE_EPOCH = 0.0; public: /** * @brief Construct a date equal to epoch. */ - LLDate(); - - /** - * @brief Construct a date equal to the source date. - */ - LLDate(const LLDate& date); + constexpr LLDate() : mSecondsSinceEpoch(DATE_EPOCH) + {} /** * @brief Construct a date from a seconds since epoch value. -- cgit v1.2.3 From 1296afd96a74877feb91690ec8dcd99b225554b8 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Thu, 4 Jul 2024 13:02:34 -0400 Subject: Reduce LLSD::Binary temporaries --- indra/llcommon/llsdutil.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index dd3a58c26d..34aa0bc070 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -51,7 +51,7 @@ // U32 LLSD ll_sd_from_U32(const U32 val) { - std::vector v; + LLSD::Binary v; U32 net_order = htonl(val); v.resize(4); @@ -63,7 +63,7 @@ LLSD ll_sd_from_U32(const U32 val) U32 ll_U32_from_sd(const LLSD& sd) { U32 ret; - std::vector v = sd.asBinary(); + const LLSD::Binary& v = sd.asBinary(); if (v.size() < 4) { return 0; @@ -76,7 +76,7 @@ U32 ll_U32_from_sd(const LLSD& sd) //U64 LLSD ll_sd_from_U64(const U64 val) { - std::vector v; + LLSD::Binary v; U32 high, low; high = (U32)(val >> 32); @@ -94,7 +94,7 @@ LLSD ll_sd_from_U64(const U64 val) U64 ll_U64_from_sd(const LLSD& sd) { U32 high, low; - std::vector v = sd.asBinary(); + const LLSD::Binary& v = sd.asBinary(); if (v.size() < 8) { @@ -112,7 +112,7 @@ U64 ll_U64_from_sd(const LLSD& sd) // IP Address (stored in net order in a U32, so don't need swizzling) LLSD ll_sd_from_ipaddr(const U32 val) { - std::vector v; + LLSD::Binary v; v.resize(4); memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */ @@ -123,7 +123,7 @@ LLSD ll_sd_from_ipaddr(const U32 val) U32 ll_ipaddr_from_sd(const LLSD& sd) { U32 ret; - std::vector v = sd.asBinary(); + const LLSD::Binary& v = sd.asBinary(); if (v.size() < 4) { return 0; @@ -135,17 +135,17 @@ U32 ll_ipaddr_from_sd(const LLSD& sd) // Converts an LLSD binary to an LLSD string LLSD ll_string_from_binary(const LLSD& sd) { - std::vector value = sd.asBinary(); + const LLSD::Binary& value = sd.asBinary(); std::string str; str.resize(value.size()); - memcpy(&str[0], &value[0], value.size()); + memcpy(&str[0], value.data(), value.size()); return str; } // Converts an LLSD string to an LLSD binary LLSD ll_binary_from_string(const LLSD& sd) { - std::vector binary_value; + LLSD::Binary binary_value; std::string string_value = sd.asString(); for (const U8 c : string_value) @@ -990,8 +990,7 @@ LLSD llsd_clone(LLSD value, LLSD filter) case LLSD::TypeBinary: { - LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end()); - clone = LLSD::Binary(bin); + clone = LLSD::Binary(value.asBinary().begin(), value.asBinary().end()); break; } default: -- cgit v1.2.3 From 38fdee7673a2067733d69244de54312c3ae200db Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Fri, 5 Jul 2024 15:01:06 -0400 Subject: Introduce move assignment and construction to LLPointer --- indra/llcommon/llpointer.h | 111 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 17 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llpointer.h b/indra/llcommon/llpointer.h index f5916f9d58..6edff9fa5e 100644 --- a/indra/llcommon/llpointer.h +++ b/indra/llcommon/llpointer.h @@ -46,8 +46,11 @@ template class LLPointer { public: + template + friend class LLPointer; + LLPointer() : - mPointer(NULL) + mPointer(nullptr) { } @@ -63,6 +66,12 @@ public: ref(); } + LLPointer(LLPointer&& ptr) noexcept + { + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + // Support conversion up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. template LLPointer(const LLPointer& ptr) : @@ -71,6 +80,13 @@ public: ref(); } + template + LLPointer(LLPointer&& ptr) noexcept : + mPointer(ptr.get()) + { + ptr.mPointer = nullptr; + } + ~LLPointer() { unref(); @@ -82,11 +98,11 @@ public: const Type& operator*() const { return *mPointer; } Type& operator*() { return *mPointer; } - operator BOOL() const { return (mPointer != NULL); } - operator bool() const { return (mPointer != NULL); } - bool operator!() const { return (mPointer == NULL); } - bool isNull() const { return (mPointer == NULL); } - bool notNull() const { return (mPointer != NULL); } + operator BOOL() const { return (mPointer != nullptr); } + operator bool() const { return (mPointer != nullptr); } + bool operator!() const { return (mPointer == nullptr); } + bool isNull() const { return (mPointer == nullptr); } + bool notNull() const { return (mPointer != nullptr); } operator Type*() const { return mPointer; } bool operator !=(Type* ptr) const { return (mPointer != ptr); } @@ -107,6 +123,17 @@ public: return *this; } + LLPointer& operator =(LLPointer&& ptr) + { + if (mPointer != ptr.mPointer) + { + unref(); + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + return *this; + } + // support assignment up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. template LLPointer& operator =(const LLPointer& ptr) @@ -115,6 +142,18 @@ public: return *this; } + template + LLPointer& operator =(LLPointer&& ptr) + { + if (mPointer != ptr.mPointer) + { + unref(); + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + return *this; + } + // Just exchange the pointers, which will not change the reference counts. static void swap(LLPointer& a, LLPointer& b) { @@ -141,9 +180,9 @@ protected: if (mPointer) { Type *temp = mPointer; - mPointer = NULL; + mPointer = nullptr; temp->unref(); - if (mPointer != NULL) + if (mPointer != nullptr) { LL_WARNS() << "Unreference did assignment to non-NULL because of destructor" << LL_ENDL; unref(); @@ -168,9 +207,11 @@ protected: template class LLConstPointer { + template + friend class LLConstPointer; public: LLConstPointer() : - mPointer(NULL) + mPointer(nullptr) { } @@ -186,6 +227,12 @@ public: ref(); } + LLConstPointer(LLConstPointer&& ptr) noexcept + { + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + // support conversion up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. template LLConstPointer(const LLConstPointer& ptr) : @@ -194,6 +241,13 @@ public: ref(); } + template + LLConstPointer(LLConstPointer&& ptr) noexcept : + mPointer(ptr.get()) + { + ptr.mPointer = nullptr; + } + ~LLConstPointer() { unref(); @@ -203,11 +257,11 @@ public: const Type* operator->() const { return mPointer; } const Type& operator*() const { return *mPointer; } - operator BOOL() const { return (mPointer != NULL); } - operator bool() const { return (mPointer != NULL); } - bool operator!() const { return (mPointer == NULL); } - bool isNull() const { return (mPointer == NULL); } - bool notNull() const { return (mPointer != NULL); } + operator BOOL() const { return (mPointer != nullptr); } + operator bool() const { return (mPointer != nullptr); } + bool operator!() const { return (mPointer == nullptr); } + bool isNull() const { return (mPointer == nullptr); } + bool notNull() const { return (mPointer != nullptr); } operator const Type*() const { return mPointer; } bool operator !=(const Type* ptr) const { return (mPointer != ptr); } @@ -239,6 +293,17 @@ public: return *this; } + LLConstPointer& operator =(LLConstPointer&& ptr) + { + if (mPointer != ptr.mPointer) + { + unref(); + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + return *this; + } + // support assignment up the type hierarchy. See Item 45 in Effective C++, 3rd Ed. template LLConstPointer& operator =(const LLConstPointer& ptr) @@ -252,6 +317,18 @@ public: return *this; } + template + LLConstPointer& operator =(LLConstPointer&& ptr) + { + if (mPointer != ptr.mPointer) + { + unref(); + mPointer = ptr.mPointer; + ptr.mPointer = nullptr; + } + return *this; + } + // Just exchange the pointers, which will not change the reference counts. static void swap(LLConstPointer& a, LLConstPointer& b) { @@ -278,9 +355,9 @@ protected: if (mPointer) { const Type *temp = mPointer; - mPointer = NULL; + mPointer = nullptr; temp->unref(); - if (mPointer != NULL) + if (mPointer != nullptr) { LL_WARNS() << "Unreference did assignment to non-NULL because of destructor" << LL_ENDL; unref(); @@ -313,7 +390,7 @@ public: : LLPointer(ptr), mStayUnique(false) { - if (ptr.mForceUnique) + if (ptr.mStayUnique) { makeUnique(); } -- cgit v1.2.3 From 9fdca96f8bd2211a99fe88e57b70cbecefa20b6d Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 8 Jul 2024 20:27:14 +0200 Subject: Re-enable compiler warnings C4244 and C4396 except for lltracerecording.h and llunittype.h for now --- indra/llcommon/llpreprocessor.h | 2 -- indra/llcommon/llqueuedthread.cpp | 2 +- indra/llcommon/llrand.cpp | 2 +- indra/llcommon/llsdparam.cpp | 2 +- indra/llcommon/llsdserialize.cpp | 6 +++--- indra/llcommon/llsdserialize_xml.cpp | 2 +- indra/llcommon/llsingleton.h | 7 ++++++- indra/llcommon/llstring.cpp | 2 +- indra/llcommon/lltimer.cpp | 2 +- indra/llcommon/lltraceaccumulators.cpp | 18 +++++++++--------- indra/llcommon/lltraceaccumulators.h | 26 +++++++++++++------------- indra/llcommon/lltracerecording.cpp | 14 +++++++------- indra/llcommon/lltracerecording.h | 9 +++++++++ indra/llcommon/llunittype.h | 9 +++++++++ indra/llcommon/tests/lleventfilter_test.cpp | 14 +++++++------- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- indra/llcommon/tests/lltrace_test.cpp | 4 ++++ indra/llcommon/tests/llunits_test.cpp | 2 +- 18 files changed, 75 insertions(+), 50 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 0248e8f8b9..0d87d1e433 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -130,8 +130,6 @@ #endif // level 4 warnings that we need to disable: -#pragma warning (disable : 4244) // possible loss of data on conversions -#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template #pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class #pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class #endif // LL_MSVC diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 7d77f6f6a9..1c4ac5a7bf 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -483,7 +483,7 @@ void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) if (sleep_time.count() > 0) { - ms_sleep(sleep_time.count()); + ms_sleep((U32)sleep_time.count()); } } processRequest(req); diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index 25d75af568..2c51e6f07f 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -85,7 +85,7 @@ inline F32 ll_internal_random() // Per Monty, it's important to clamp using the correct fmodf() rather // than expanding to F64 for fmod() and then truncating back to F32. Prior // to this change, we were getting sporadic ll_frand() == 1.0 results. - F32 rv{ narrow(gRandomGenerator()) }; + F32 rv{ narrow(gRandomGenerator()) }; if(!((rv >= 0.0f) && (rv < 1.0f))) return fmodf(rv, 1.0f); return rv; } diff --git a/indra/llcommon/llsdparam.cpp b/indra/llcommon/llsdparam.cpp index b981be4d0a..3ae153a67c 100644 --- a/indra/llcommon/llsdparam.cpp +++ b/indra/llcommon/llsdparam.cpp @@ -149,7 +149,7 @@ bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) { LLParamSDParser& self = static_cast(parser); - *((F32*)val_ptr) = self.mCurReadSD->asReal(); + *((F32*)val_ptr) = (F32)self.mCurReadSD->asReal(); return true; } diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 15002580c9..5e267c6805 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -231,7 +231,7 @@ bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes) } // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that // data to whatever remains in 'str'. - LLMemoryStreamBuf already(reinterpret_cast(hdr_buf), inbuf); + LLMemoryStreamBuf already(reinterpret_cast(hdr_buf), (S32)inbuf); cat_streambuf prebuff(&already, str.rdbuf()); std::istream prepend(&prebuff); #if 1 @@ -566,7 +566,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c data, NOTATION_FALSE_SERIAL, false); - if(PARSE_FAILURE == cnt) parse_count = cnt; + if(PARSE_FAILURE == cnt) parse_count = (S32)cnt; else account(cnt); } else @@ -592,7 +592,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c if(isalpha(c)) { auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true); - if(PARSE_FAILURE == cnt) parse_count = cnt; + if(PARSE_FAILURE == cnt) parse_count = (S32)cnt; else account(cnt); } else diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index 88cbb3b984..dd00c39180 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -554,7 +554,7 @@ void LLSDXMLParser::Impl::parsePart(const char* buf, llssize len) if ( buf != NULL && len > 0 ) { - XML_Status status = XML_Parse(mParser, buf, len, false); + XML_Status status = XML_Parse(mParser, buf, (int)len, 0); if (status == XML_STATUS_ERROR) { LL_INFOS() << "Unexpected XML parsing error at start" << LL_ENDL; diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 7c6be25309..316831cd74 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -37,7 +37,8 @@ #include "llmainthreadtask.h" #ifdef LL_WINDOWS -#pragma warning( disable : 4506 ) // no definition for inline function +#pragma warning(push) +#pragma warning(disable : 4506) // no definition for inline function #endif class LLSingletonBase: private boost::noncopyable @@ -861,4 +862,8 @@ private: template T* LLSimpleton::sInstance{ nullptr }; +#ifdef LL_WINDOWS +#pragma warning(pop) +#endif + #endif diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 6f3d193d6b..c57f8b1e96 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -250,7 +250,7 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) while (i < len) { llwchar cur_char; - i += utf16chars_to_wchar(chars16+i, &cur_char); + i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); wout += cur_char; } return wout; diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index a3e871661c..e5c0970d35 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -101,7 +101,7 @@ U32 micro_sleep(U64 us, U32 max_yields) WaitForSingleObject(timer, INFINITE); CloseHandle(timer); #else - Sleep(us / 1000); + Sleep((DWORD)(us / 1000)); #endif return 0; diff --git a/indra/llcommon/lltraceaccumulators.cpp b/indra/llcommon/lltraceaccumulators.cpp index 8741087f3a..dc9a87eb80 100644 --- a/indra/llcommon/lltraceaccumulators.cpp +++ b/indra/llcommon/lltraceaccumulators.cpp @@ -100,7 +100,7 @@ bool AccumulatorBufferGroup::isCurrent() const return mCounts.isCurrent(); } -void AccumulatorBufferGroup::append( const AccumulatorBufferGroup& other ) +void AccumulatorBufferGroup::append(const AccumulatorBufferGroup& other) { LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; mCounts.addSamples(other.mCounts, SEQUENTIAL); @@ -109,7 +109,7 @@ void AccumulatorBufferGroup::append( const AccumulatorBufferGroup& other ) mStackTimers.addSamples(other.mStackTimers, SEQUENTIAL); } -void AccumulatorBufferGroup::merge( const AccumulatorBufferGroup& other) +void AccumulatorBufferGroup::merge(const AccumulatorBufferGroup& other) { LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; mCounts.addSamples(other.mCounts, NON_SEQUENTIAL); @@ -140,7 +140,7 @@ void AccumulatorBufferGroup::sync() F64 SampleAccumulator::mergeSumsOfSquares(const SampleAccumulator& a, const SampleAccumulator& b) { - const F64 epsilon = 0.0000001; + constexpr F64 epsilon = 0.0000001; if (a.getSamplingTime() > epsilon && b.getSamplingTime() > epsilon) { @@ -170,7 +170,7 @@ F64 SampleAccumulator::mergeSumsOfSquares(const SampleAccumulator& a, const Samp return a.getSumOfSquares(); } -void SampleAccumulator::addSamples( const SampleAccumulator& other, EBufferAppendType append_type ) +void SampleAccumulator::addSamples(const SampleAccumulator& other, EBufferAppendType append_type) { if (append_type == NON_SEQUENTIAL) { @@ -205,7 +205,7 @@ void SampleAccumulator::addSamples( const SampleAccumulator& other, EBufferAppen } } -void SampleAccumulator::reset( const SampleAccumulator* other ) +void SampleAccumulator::reset(const SampleAccumulator* other) { mLastValue = other ? other->mLastValue : NaN; mHasValue = other ? other->mHasValue : false; @@ -243,7 +243,7 @@ F64 EventAccumulator::mergeSumsOfSquares(const EventAccumulator& a, const EventA return a.mSumOfSquares; } -void EventAccumulator::addSamples( const EventAccumulator& other, EBufferAppendType append_type ) +void EventAccumulator::addSamples(const EventAccumulator& other, EBufferAppendType append_type) { if (other.mNumSamples) { @@ -269,12 +269,12 @@ void EventAccumulator::addSamples( const EventAccumulator& other, EBufferAppendT } } -void EventAccumulator::reset( const EventAccumulator* other ) +void EventAccumulator::reset(const EventAccumulator* other) { mNumSamples = 0; mSum = 0; - mMin = F32(NaN); - mMax = F32(NaN); + mMin = NaN; + mMax = NaN; mMean = NaN; mSumOfSquares = 0; mLastValue = other ? other->mLastValue : NaN; diff --git a/indra/llcommon/lltraceaccumulators.h b/indra/llcommon/lltraceaccumulators.h index ba7acf9547..0a2e2bf997 100644 --- a/indra/llcommon/lltraceaccumulators.h +++ b/indra/llcommon/lltraceaccumulators.h @@ -39,7 +39,7 @@ namespace LLTrace { - const F64 NaN = std::numeric_limits::quiet_NaN(); + constexpr F64 NaN = std::numeric_limits::quiet_NaN(); enum EBufferAppendType { @@ -251,8 +251,8 @@ namespace LLTrace EventAccumulator() : mSum(0), - mMin(F32(NaN)), - mMax(F32(NaN)), + mMin(NaN), + mMax(NaN), mMean(NaN), mSumOfSquares(0), mNumSamples(0), @@ -288,11 +288,11 @@ namespace LLTrace void sync(F64SecondsImplicit) {} F64 getSum() const { return mSum; } - F32 getMin() const { return mMin; } - F32 getMax() const { return mMax; } + F64 getMin() const { return mMin; } + F64 getMax() const { return mMax; } F64 getLastValue() const { return mLastValue; } F64 getMean() const { return mMean; } - F64 getStandardDeviation() const { return sqrtf(mSumOfSquares / mNumSamples); } + F64 getStandardDeviation() const { return sqrt(mSumOfSquares / mNumSamples); } F64 getSumOfSquares() const { return mSumOfSquares; } S32 getSampleCount() const { return mNumSamples; } bool hasValue() const { return mNumSamples > 0; } @@ -307,7 +307,7 @@ namespace LLTrace F64 mMean, mSumOfSquares; - F32 mMin, + F64 mMin, mMax; S32 mNumSamples; @@ -322,8 +322,8 @@ namespace LLTrace SampleAccumulator() : mSum(0), - mMin(F32(NaN)), - mMax(F32(NaN)), + mMin(NaN), + mMax(NaN), mMean(NaN), mSumOfSquares(0), mLastSampleTimeStamp(0), @@ -378,11 +378,11 @@ namespace LLTrace } F64 getSum() const { return mSum; } - F32 getMin() const { return mMin; } - F32 getMax() const { return mMax; } + F64 getMin() const { return mMin; } + F64 getMax() const { return mMax; } F64 getLastValue() const { return mLastValue; } F64 getMean() const { return mMean; } - F64 getStandardDeviation() const { return sqrtf(mSumOfSquares / mTotalSamplingTime); } + F64 getStandardDeviation() const { return sqrt(mSumOfSquares / mTotalSamplingTime); } F64 getSumOfSquares() const { return mSumOfSquares; } F64SecondsImplicit getSamplingTime() const { return mTotalSamplingTime; } S32 getSampleCount() const { return mNumSamples; } @@ -402,7 +402,7 @@ namespace LLTrace mLastSampleTimeStamp, mTotalSamplingTime; - F32 mMin, + F64 mMin, mMax; S32 mNumSamples; diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp index 1ec83be7cb..c23adca7e8 100644 --- a/indra/llcommon/lltracerecording.cpp +++ b/indra/llcommon/lltracerecording.cpp @@ -229,7 +229,7 @@ F32 Recording::getPerSec(const StatType& s update(); const TimeBlockAccumulator& accumulator = mBuffers->mStackTimers[stat.getIndex()]; const TimeBlockAccumulator* active_accumulator = mActiveBuffers ? &mActiveBuffers->mStackTimers[stat.getIndex()] : NULL; - return (F32)(accumulator.mCalls + (active_accumulator ? active_accumulator->mCalls : 0)) / mElapsedSeconds.value(); + return (F32)(accumulator.mCalls + (active_accumulator ? active_accumulator->mCalls : 0)) / (F32)mElapsedSeconds.value(); } bool Recording::hasValue(const StatType& stat) @@ -296,11 +296,11 @@ F64 Recording::getMean( const StatType& stat ) const SampleAccumulator* active_accumulator = mActiveBuffers ? &mActiveBuffers->mSamples[stat.getIndex()] : NULL; if (active_accumulator && active_accumulator->hasValue()) { - F32 t = 0.0f; + F64 t = 0.0; S32 div = accumulator.getSampleCount() + active_accumulator->getSampleCount(); if (div > 0) { - t = active_accumulator->getSampleCount() / div; + t = (F64)active_accumulator->getSampleCount() / (F64)div; } return lerp(accumulator.getMean(), active_accumulator->getMean(), t); } @@ -319,7 +319,7 @@ F64 Recording::getStandardDeviation( const StatType& stat ) if (active_accumulator && active_accumulator->hasValue()) { F64 sum_of_squares = SampleAccumulator::mergeSumsOfSquares(accumulator, *active_accumulator); - return sqrtf(sum_of_squares / (accumulator.getSamplingTime() + active_accumulator->getSamplingTime())); + return sqrt(sum_of_squares / (F64)(accumulator.getSamplingTime() + active_accumulator->getSamplingTime())); } else { @@ -382,11 +382,11 @@ F64 Recording::getMean( const StatType& stat ) const EventAccumulator* active_accumulator = mActiveBuffers ? &mActiveBuffers->mEvents[stat.getIndex()] : NULL; if (active_accumulator && active_accumulator->hasValue()) { - F32 t = 0.0f; + F64 t = 0.0; S32 div = accumulator.getSampleCount() + active_accumulator->getSampleCount(); if (div > 0) { - t = active_accumulator->getSampleCount() / div; + t = (F64)active_accumulator->getSampleCount() / (F64)div; } return lerp(accumulator.getMean(), active_accumulator->getMean(), t); } @@ -405,7 +405,7 @@ F64 Recording::getStandardDeviation( const StatType& stat ) if (active_accumulator && active_accumulator->hasValue()) { F64 sum_of_squares = EventAccumulator::mergeSumsOfSquares(accumulator, *active_accumulator); - return sqrtf(sum_of_squares / (accumulator.getSampleCount() + active_accumulator->getSampleCount())); + return sqrt(sum_of_squares / (F64)(accumulator.getSampleCount() + active_accumulator->getSampleCount())); } else { diff --git a/indra/llcommon/lltracerecording.h b/indra/llcommon/lltracerecording.h index 985f06cd59..ad4c91d85b 100644 --- a/indra/llcommon/lltracerecording.h +++ b/indra/llcommon/lltracerecording.h @@ -35,6 +35,11 @@ #include "llpointer.h" #include +#ifdef LL_WINDOWS +#pragma warning(push) +#pragma warning(disable : 4244) // possible loss of data on conversions +#endif + class LLStopWatchControlsMixinCommon { public: @@ -714,4 +719,8 @@ namespace LLTrace }; } +#ifdef LL_WINDOWS +#pragma warning(pop) +#endif + #endif // LL_LLTRACERECORDING_H diff --git a/indra/llcommon/llunittype.h b/indra/llcommon/llunittype.h index 83ce0d05a8..bb1408609a 100644 --- a/indra/llcommon/llunittype.h +++ b/indra/llcommon/llunittype.h @@ -31,6 +31,11 @@ #include "llpreprocessor.h" #include "llerror.h" +#ifdef LL_WINDOWS +#pragma warning(push) +#pragma warning(disable : 4244) // possible loss of data on conversions +#endif + //lightweight replacement of type traits for simple type equality check template struct LLIsSameType @@ -846,4 +851,8 @@ LL_FORCE_INLINE S2 ll_convert_units(LLUnit in, LLUnit U64##unit_name; \ typedef LLUnitImplicit U64##unit_name##Implicit +#ifdef LL_WINDOWS +#pragma warning(pop) +#endif + #endif //LL_UNITTYPE_H diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index a01d7fe415..d7b80e2545 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -81,13 +81,13 @@ class TestEventThrottle: public LLEventThrottleBase public: TestEventThrottle(F32 interval): LLEventThrottleBase(interval), - mAlarmRemaining(-1), - mTimerRemaining(-1) + mAlarmRemaining(-1.f), + mTimerRemaining(-1.f) {} TestEventThrottle(LLEventPump& source, F32 interval): LLEventThrottleBase(source, interval), - mAlarmRemaining(-1), - mTimerRemaining(-1) + mAlarmRemaining(-1.f), + mTimerRemaining(-1.f) {} /*----- implementation of LLEventThrottleBase timing functionality -----*/ @@ -100,12 +100,12 @@ public: virtual bool alarmRunning() const /*override*/ { // decrementing to exactly 0 should mean the alarm fires - return mAlarmRemaining > 0; + return mAlarmRemaining > 0.f; } virtual void alarmCancel() /*override*/ { - mAlarmRemaining = -1; + mAlarmRemaining = -1.f; } virtual void timerSet(F32 interval) /*override*/ @@ -116,7 +116,7 @@ public: virtual F32 timerGetRemaining() const /*override*/ { // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired - return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0; + return (mTimerRemaining > 0.0f)? mTimerRemaining : 0.0f; } /*------------------- methods for manipulating time --------------------*/ diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index fb2af1d2db..fae9f7023f 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1809,7 +1809,7 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), + int rc = (int)_spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), std::forward(args)..., NULL); if (rc == -1) { diff --git a/indra/llcommon/tests/lltrace_test.cpp b/indra/llcommon/tests/lltrace_test.cpp index 8851f87b91..923a67ac8e 100644 --- a/indra/llcommon/tests/lltrace_test.cpp +++ b/indra/llcommon/tests/lltrace_test.cpp @@ -32,6 +32,10 @@ #include "lltracerecording.h" #include "../test/lltut.h" +#ifdef LL_WINDOWS +#pragma warning(disable : 4244) // possible loss of data on conversions +#endif + namespace LLUnits { // using powers of 2 to allow strict floating point equality diff --git a/indra/llcommon/tests/llunits_test.cpp b/indra/llcommon/tests/llunits_test.cpp index 49f2d3085a..98a58eb47e 100644 --- a/indra/llcommon/tests/llunits_test.cpp +++ b/indra/llcommon/tests/llunits_test.cpp @@ -262,7 +262,7 @@ namespace tut F32 float_val = quatloos_implicit; ensure("implicit units convert implicitly to regular values", float_val == 16); - S32 int_val = quatloos_implicit; + S32 int_val = (S32)quatloos_implicit; ensure("implicit units convert implicitly to regular values", int_val == 16); // conversion of implicits -- cgit v1.2.3 From 499a717f62458ecddac270f069d283cf2ed62712 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 8 Jul 2024 20:46:45 +0200 Subject: Remove ancient MSVC7 code --- indra/llcommon/llpreprocessor.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 0248e8f8b9..34816acc23 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -74,9 +74,6 @@ #ifndef LL_MSVC #define LL_MSVC 1 #endif - #if _MSC_VER < 1400 - #define LL_MSVC7 //Visual C++ 2003 or earlier - #endif #endif // Deal with minor differences on Unixy OSes. -- cgit v1.2.3 From 989cfe2f70441fe02222d369e84118a94dc96890 Mon Sep 17 00:00:00 2001 From: Henri Beauchamp Date: Mon, 8 Jul 2024 23:18:02 +0200 Subject: Fix for crash in XMLRPC reply decoding on login with large inventories Commit 2ea5ac0c43e3e28d2b1774f5367d099271a1da32 introduced a crash bug due to the recursive construction of the XMLTreeNode wrapper class. The constructor of the said class typically recurses twice as many times as there are entries in the user's inventory list. This commit: - Moves the fromXMLRPCValue() method and its helper functions from the LLSD class/module to the LLXMLNode class, where it belongs, thus making LLSD::TreeNode (which was a wrapper class to avoid making llcommon dependant on llxml, which is still the case after this commit) totally moot; the fromXMLRPCValue() call is now done directly on the LLXMLNode. - Moves the XML and XMLRPC decoding code out of the HTTP coroutine LLXMLRPCTransaction::Handler (coroutines got an even smaller and fixed stack), and into LLXMLRPCTransaction::Impl::process(). - Removes XMLTreeNode entirely, fixing the crash as a result. --- indra/llcommon/llsd.cpp | 147 ------------------------------------------------ indra/llcommon/llsd.h | 10 ---- 2 files changed, 157 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 2bbe06e72f..77fe545c3f 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -1018,153 +1018,6 @@ const LLSD::String& LLSD::asStringRef() const { return safe(impl).asStringRef(); LLSD::String LLSD::asXMLRPCValue() const { return "" + safe(impl).asXMLRPCValue() + ""; } -static bool inline check(bool condition, const char* warning_message) -{ - if (!condition) - { - LL_WARNS() << warning_message << LL_ENDL; - } - - return condition; -} - -static bool parseXMLRPCArrayValue(LLSD& target, LLSD::TreeNode* node) -{ - LLSD::TreeNode* data = node->getFirstChild(); - if (!check(data, "No array inner XML element ( expected)") || - !check(data->hasName("data"), "Invalid array inner XML element ( expected)") || - !check(!data->getNextSibling(), "Multiple array inner XML elements (single expected)")) - return false; - - for (LLSD::TreeNode* item = data->getFirstChild(); item; item = item->getNextSibling()) - { - LLSD value; - if (!value.fromXMLRPCValue(item)) - return false; - - target.append(value); - } - - return true; -} - -static bool parseXMLRPCStructValue(LLSD& target, LLSD::TreeNode* node) -{ - for (LLSD::TreeNode* item = node->getFirstChild(); item; item = item->getNextSibling()) - { - if (!check(item->hasName("member"), "Invalid struct inner XML element ( expected)")) - return false; - - std::string name; - LLSD value; - for (LLSD::TreeNode* subitem = item->getFirstChild(); subitem; subitem = subitem->getNextSibling()) - { - if (subitem->hasName("name")) - { - name = LLStringFn::xml_decode(subitem->getTextContents()); - } - else if (!value.fromXMLRPCValue(subitem)) - { - return false; - } - } - if (!check(!name.empty(), "Empty struct member name")) - return false; - - target.insert(name, value); - } - - return true; -} - -bool LLSD::fromXMLRPCValue(TreeNode* node) -{ - clear(); - - llassert(node); - if (!node) - return false; - - if (!check(node->hasName("value"), "Invalid XML element ( expected)")) - return false; - - TreeNode* inner = node->getFirstChild(); - if (!inner) - { - check(false, "No inner XML element (value type expected)"); - // Value with no type qualifier is treated as string - assign(LLStringFn::xml_decode(node->getTextContents())); - return true; - } - - if (!check(!inner->getNextSibling(), "Multiple inner XML elements (single expected)")) - return false; - - if (inner->hasName("string")) - { - assign(LLStringFn::xml_decode(inner->getTextContents())); - return true; - } - - if (inner->hasName("int") || inner->hasName("i4")) - { - assign(std::stoi(inner->getTextContents())); - return true; - } - - if (inner->hasName("double")) - { - assign(std::stod(inner->getTextContents())); - return true; - } - - if (inner->hasName("boolean")) - { - assign(!!std::stoi(inner->getTextContents())); - return true; - } - - if (inner->hasName("dateTime.iso8601")) - { - assign(Date(inner->getTextContents())); - return true; - } - - if (inner->hasName("base64")) - { - std::string decoded = LLBase64::decodeAsString(inner->getTextContents()); - Binary binary(decoded.size()); - memcpy(binary.data(), decoded.data(), decoded.size()); - assign(binary); - return true; - } - - if (inner->hasName("array")) - { - if (!parseXMLRPCArrayValue(*this, inner)) - { - clear(); - return false; - } - return true; - } - - if (inner->hasName("struct")) - { - if (!parseXMLRPCStructValue(*this, inner)) - { - clear(); - return false; - } - return true; - } - - check(false, "Unknown inner XML element (known value type expected)"); - // Value with unknown type qualifier is treated as string - assign(LLStringFn::xml_decode(inner->getTextContents())); - return true; -} - // const char * helpers LLSD::LLSD(const char* v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } void LLSD::assign(const char* v) diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index 781e8d58e9..d2b3548831 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -290,16 +290,6 @@ public: // See http://xmlrpc.com/spec.md String asXMLRPCValue() const; - struct TreeNode - { - virtual bool hasName(const String& name) const = 0; - virtual String getTextContents() const = 0; - virtual TreeNode* getFirstChild() const = 0; - virtual TreeNode* getNextSibling() const = 0; - }; - - bool fromXMLRPCValue(TreeNode* node); - operator Boolean() const { return asBoolean(); } operator Integer() const { return asInteger(); } operator Real() const { return asReal(); } -- cgit v1.2.3 From f1f7426d5436bd019213fb9d086f513688ee5fe1 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 9 Jul 2024 15:36:00 -0500 Subject: #1826 Fix for LL_ERRS crashing in mutex lock --- indra/llcommon/llerror.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index a45bc32028..fa24c2eaf4 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -55,6 +55,7 @@ #include "llsingleton.h" #include "llstl.h" #include "lltimer.h" +#include // On Mac, got: // #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define @@ -506,7 +507,7 @@ namespace LLError::TimeFunction mTimeFunction; Recorders mRecorders; - LLCoros::Mutex mRecorderMutex; + boost::fibers::recursive_mutex mRecorderMutex; int mShouldLogCallCounter; @@ -1044,7 +1045,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLCoros::LockType lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); s->mRecorders.push_back(recorder); } @@ -1055,7 +1056,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLCoros::LockType lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), s->mRecorders.end()); } @@ -1104,7 +1105,7 @@ namespace LLError std::shared_ptr findRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLCoros::LockType lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); return findRecorderPos(s).first; } @@ -1115,7 +1116,7 @@ namespace LLError bool removeRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLCoros::LockType lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); auto found = findRecorderPos(s); if (found.first) { @@ -1221,7 +1222,7 @@ namespace std::string escaped_message; - LLCoros::LockType lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); for (LLError::RecorderPtr& r : s->mRecorders) { if (!r->enabled()) -- cgit v1.2.3 From d9782cf9660bd08b2577d79b29e93129c838de3b Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 9 Jul 2024 15:53:06 -0500 Subject: Review feedback change --- indra/llcommon/llerror.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index fa24c2eaf4..fa24987d1c 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1045,7 +1045,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); s->mRecorders.push_back(recorder); } @@ -1056,7 +1056,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), s->mRecorders.end()); } @@ -1105,7 +1105,7 @@ namespace LLError std::shared_ptr findRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); return findRecorderPos(s).first; } @@ -1116,7 +1116,7 @@ namespace LLError bool removeRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); auto found = findRecorderPos(s); if (found.first) { @@ -1222,7 +1222,7 @@ namespace std::string escaped_message; - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); for (LLError::RecorderPtr& r : s->mRecorders) { if (!r->enabled()) -- cgit v1.2.3 From e6e41e71b7bc860faee8cd13c56f7180e8eb4745 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 9 Jul 2024 15:54:18 -0500 Subject: #1943 make sys free in texture console llmemorysavailphysicalmeminkb tell the truth (#1966) * Also fix for crash when applying MoaP to PBR material --- indra/llcommon/llmemory.cpp | 74 ++++++++++++++++++++------------------------- indra/llcommon/llsys.cpp | 49 +++++++++++++++--------------- indra/llcommon/llsys.h | 5 +-- 3 files changed, 60 insertions(+), 68 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 104c40f0d7..81e8073dbf 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -51,13 +51,28 @@ //---------------------------------------------------------------------------- //static + +// most important memory metric for texture streaming +// On Windows, this should agree with resource monitor -> performance -> memory -> available +// On OS X, this should be activity monitor -> memory -> (physical memory - memory used) +// NOTE: this number MAY be less than the actual available memory on systems with more than MaxHeapSize64 GB of physical memory (default 16GB) +// In that case, should report min(available, sMaxHeapSizeInKB-sAllocateMemInKB) U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); + +// Installed physical memory U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0); -static LLTrace::SampleStatHandle sAllocatedMem("allocated_mem", "active memory in use by application"); -static LLTrace::SampleStatHandle sVirtualMem("virtual_mem", "virtual memory assigned to application"); + +// Maximimum heap size according to the user's settings (default 16GB) +U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); + +// Current memory usage U32Kilobytes LLMemory::sAllocatedMemInKB(0); + U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); -U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); + + +static LLTrace::SampleStatHandle sAllocatedMem("allocated_mem", "active memory in use by application"); +static LLTrace::SampleStatHandle sVirtualMem("virtual_mem", "virtual memory assigned to application"); void ll_assert_aligned_func(uintptr_t ptr,U32 alignment) { @@ -86,7 +101,14 @@ void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) void LLMemory::updateMemoryInfo() { LL_PROFILE_ZONE_SCOPED - U32Kilobytes avail_phys; + + + sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB(); + + U32Kilobytes avail_mem; + LLMemoryInfo::getAvailableMemoryKB(avail_mem); + sAvailPhysicalMemInKB = avail_mem; + #if LL_WINDOWS PROCESS_MEMORY_COUNTERS counters; @@ -99,8 +121,6 @@ void LLMemory::updateMemoryInfo() sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); sample(sVirtualMem, sAllocatedPageSizeInKB); - U32Kilobytes avail_virtual; - LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; #elif defined(LL_DARWIN) task_vm_info info; @@ -126,50 +146,20 @@ void LLMemory::updateMemoryInfo() { LL_WARNS() << "task_info failed" << LL_ENDL; } - - // Total installed and available physical memory are properties of the host, not just our process. - vm_statistics64_data_t vmstat; - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; - mach_port_t host = mach_host_self(); - vm_size_t page_size; - host_page_size(host, &page_size); - kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast(&vmstat), &count); - if (result == KERN_SUCCESS) { - // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' - // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. - avail_phys = U32Bytes(vmstat.free_count * page_size); - sMaxHeapSizeInKB = LLMemoryInfo::getHardwareMemSize(); - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } #elif defined(LL_LINUX) // Use sysinfo() to get the total physical memory. struct sysinfo info; sysinfo(&info); - sMaxHeapSizeInKB = U32Kilobytes::convert((U64Bytes)info.totalram); // Total RAM in system - avail_phys = U32Kilobytes::convert((U64Bytes)info.freeram); // Total Free RAM in system sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(LLMemory::getCurrentRSS())); // represents the RAM allocated by this process only (in line with the windows implementation) #else //not valid for other systems for now. LL_WARNS() << "LLMemory::updateMemoryInfo() not implemented for this platform." << LL_ENDL; sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); - sMaxPhysicalMemInKB = U64Bytes(U32_MAX); - sAvailPhysicalMemInKB = U64Bytes(U32_MAX); #endif sample(sAllocatedMem, sAllocatedMemInKB); - // sMaxPhysicalMem - max this process can use = the lesser of (what we already have + what's available) or MaxHeap - sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); - if(sMaxPhysicalMemInKB > sAllocatedMemInKB) - { - sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; - } - else - { - sAvailPhysicalMemInKB = U32Kilobytes(0); - } + sAvailPhysicalMemInKB = llmin(sAvailPhysicalMemInKB, sMaxHeapSizeInKB - sAllocatedMemInKB); + return ; } @@ -206,10 +196,10 @@ void LLMemory::logMemoryInfo(bool update) updateMemoryInfo() ; } - LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ; - LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ; - LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ; - LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ; + LL_INFOS() << llformat("Current allocated physical memory: %.2f MB", sAllocatedMemInKB / 1024.0) << LL_ENDL; + LL_INFOS() << llformat("Current allocated page size: %.2f MB", sAllocatedPageSizeInKB / 1024.0) << LL_ENDL; + LL_INFOS() << llformat("Current available physical memory: %.2f MB", sAvailPhysicalMemInKB / 1024.0) << LL_ENDL; + LL_INFOS() << llformat("Current max usable memory: %.2f MB", sMaxPhysicalMemInKB / 1024.0) << LL_ENDL; } //static diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index cfb05873df..8bee33be0a 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -74,6 +74,8 @@ using namespace llsd; # include # include # include +# include +# include #elif LL_LINUX # include # include @@ -85,6 +87,7 @@ const char MEMINFO_FILE[] = "/proc/meminfo"; #endif LLCPUInfo gSysCPU; +LLMemoryInfo gSysMemory; // Don't log memory info any more often than this. It also serves as our // framerate sample size. @@ -797,33 +800,32 @@ U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const } //static -void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) +void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; #if LL_WINDOWS // Sigh, this shouldn't be a static method, then we wouldn't have to // reload this data separately from refresh() LLSD statsMap(loadStatsMap()); - avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); - avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger(); + avail_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); #elif LL_DARWIN - // mStatsMap is derived from vm_stat, look for (e.g.) "kb free": - // $ vm_stat - // Mach Virtual Memory Statistics: (page size of 4096 bytes) - // Pages free: 462078. - // Pages active: 142010. - // Pages inactive: 220007. - // Pages wired down: 159552. - // "Translation faults": 220825184. - // Pages copy-on-write: 2104153. - // Pages zero filled: 167034876. - // Pages reactivated: 65153. - // Pageins: 2097212. - // Pageouts: 41759. - // Object cache: 841598 hits of 7629869 lookups (11% hit rate) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; + // use host_statistics64 to get memory info + vm_statistics64_data_t vmstat; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + mach_port_t host = mach_host_self(); + vm_size_t page_size; + host_page_size(host, &page_size); + kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast(&vmstat), &count); + if (result == KERN_SUCCESS) + { + avail_mem_kb = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size); + } + else + { + avail_mem_kb = (U32Kilobytes)-1; + } #elif LL_LINUX // mStatsMap is derived from MEMINFO_FILE: @@ -874,15 +876,14 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32 // DirectMap4k: 434168 kB // DirectMap2M: 477184 kB // (could also run 'free', but easier to read a file than run a program) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; + LLSD statsMap(loadStatsMap()); + avail_mem_kb = (U32Kilobytes)statsMap["MemFree"].asInteger(); #else //do not know how to collect available memory info for other systems. //leave it blank here for now. - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; + avail_mem_kb = (U32Kilobytes)-1 ; #endif } @@ -977,7 +978,7 @@ LLSD LLMemoryInfo::loadStatsMap() // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the // pointer. - GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); + GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*) &pmem, sizeof(pmem)); stats.add("Page Fault Count", pmem.PageFaultCount); stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index f97d49eeb1..827b0dc048 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -134,8 +134,8 @@ public: static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. #endif - //get the available memory infomation in KiloBytes. - static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); + //get the available memory in KiloBytes. + static void getAvailableMemoryKB(U32Kilobytes& avail_mem_kb); // Retrieve a map of memory statistics. The keys of the map are platform- // dependent. The values are in kilobytes to try to avoid integer overflow. @@ -169,6 +169,7 @@ bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& ds // gzip srcfile into dstfile. Returns false on error. bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); +extern LL_COMMON_API LLMemoryInfo gSysMemory; extern LL_COMMON_API LLCPUInfo gSysCPU; #endif // LL_LLSYS_H -- cgit v1.2.3 From 2a7030992faa12c362d3eb9365080efd8265e15f Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Tue, 9 Jul 2024 17:53:43 -0400 Subject: Update tracy profiler to 0.10 (#1946) --- indra/llcommon/llerror.cpp | 16 ++++++++-------- indra/llcommon/llerrorcontrol.h | 2 +- indra/llcommon/lleventfilter.h | 2 +- indra/llcommon/llmemory.cpp | 5 ++--- indra/llcommon/llmutex.cpp | 36 ++++++++++++++++++------------------ indra/llcommon/llprofiler.h | 2 +- indra/llcommon/llsdserialize.cpp | 10 +++++----- indra/llcommon/llsdserialize_xml.cpp | 2 +- indra/llcommon/llsdutil.cpp | 22 +++++++++++----------- indra/llcommon/llsys.cpp | 2 +- indra/llcommon/llthread.cpp | 14 +++++++------- indra/llcommon/lltimer.cpp | 4 ++-- 12 files changed, 58 insertions(+), 59 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index a45bc32028..800586b6a6 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -167,7 +167,7 @@ namespace { virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; if (LLError::getAlwaysFlush()) { mFile << message << std::endl; @@ -234,7 +234,7 @@ namespace { virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; // The default colors for error, warn and debug are now a bit more pastel // and easier to read on the default (black) terminal background but you // now have the option to set the color of each via an environment variables: @@ -274,7 +274,7 @@ namespace { LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; static std::string s_ansi_bold = createBoldANSI(); // bold text static std::string s_ansi_reset = createResetANSI(); // reset // ANSI color code escape sequence, message, and reset in one fprintf call @@ -311,7 +311,7 @@ namespace { virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; mBuffer->addLine(message); } @@ -338,7 +338,7 @@ namespace { virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; debugger_print(message); } }; @@ -1215,7 +1215,7 @@ namespace void writeToRecorders(const LLError::CallSite& site, const std::string& message) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; LLError::ELevel level = site.mLevel; SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); @@ -1346,7 +1346,7 @@ namespace LLError bool Log::shouldLog(CallSite& site) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { @@ -1391,7 +1391,7 @@ namespace LLError void Log::flush(const std::ostringstream& out, const CallSite& site) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; LLMutexTrylock lock(getMutex(),5); if (!lock.isLocked()) { diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index bf5a6df556..2be443ca37 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -190,7 +190,7 @@ namespace LLError {} void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; mCallable(level, message); } private: diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 5c45144fad..d8c7e15a27 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -429,7 +429,7 @@ public: // path, then stores it to mTarget. virtual bool post(const LLSD& event) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // Extract the element specified by 'mPath' from 'event'. To perform a // generic type-appropriate store through mTarget, construct an diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 81e8073dbf..99c803e46f 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -100,8 +100,7 @@ void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) //static void LLMemory::updateMemoryInfo() { - LL_PROFILE_ZONE_SCOPED - + LL_PROFILE_ZONE_SCOPED; sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB(); @@ -190,7 +189,7 @@ void* LLMemory::tryToAlloc(void* address, U32 size) //static void LLMemory::logMemoryInfo(bool update) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; if(update) { updateMemoryInfo() ; diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 40c651d9c1..be1ae89a25 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -100,7 +100,7 @@ void LLMutex::unlock() bool LLMutex::isLocked() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if (!mMutex.try_lock()) { return true; @@ -124,7 +124,7 @@ LLThread::id_t LLMutex::lockingThread() const bool LLMutex::trylock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if (isSelfLocked()) { //redundant lock mCount++; @@ -161,7 +161,7 @@ LLSharedMutex::LLSharedMutex() bool LLSharedMutex::isLocked() const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; std::lock_guard lock(mLockMutex); return !mLockingThreads.empty(); @@ -169,7 +169,7 @@ bool LLSharedMutex::isLocked() const bool LLSharedMutex::isThreadLocked() const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); std::lock_guard lock(mLockMutex); @@ -179,7 +179,7 @@ bool LLSharedMutex::isThreadLocked() const void LLSharedMutex::lockShared() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); mLockMutex.lock(); @@ -204,7 +204,7 @@ void LLSharedMutex::lockShared() void LLSharedMutex::lockExclusive() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); mLockMutex.lock(); @@ -237,7 +237,7 @@ void LLSharedMutex::lockExclusive() bool LLSharedMutex::trylockShared() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); std::lock_guard lock(mLockMutex); @@ -260,7 +260,7 @@ bool LLSharedMutex::trylockShared() bool LLSharedMutex::trylockExclusive() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); std::lock_guard lock(mLockMutex); @@ -282,7 +282,7 @@ bool LLSharedMutex::trylockExclusive() void LLSharedMutex::unlockShared() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); std::lock_guard lock(mLockMutex); @@ -303,7 +303,7 @@ void LLSharedMutex::unlockShared() void LLSharedMutex::unlockExclusive() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; LLThread::id_t current_thread = LLThread::currentID(); std::lock_guard lock(mLockMutex); @@ -338,20 +338,20 @@ LLCondition::~LLCondition() void LLCondition::wait() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; std::unique_lock< std::mutex > lock(mMutex); mCond.wait(lock); } void LLCondition::signal() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mCond.notify_one(); } void LLCondition::broadcast() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mCond.notify_all(); } @@ -364,7 +364,7 @@ LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) : mMutex(mutex), mLocked(false) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if (mMutex) mLocked = mMutex->trylock(); } @@ -373,7 +373,7 @@ LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) : mMutex(mutex), mLocked(false) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if (!mMutex) return; @@ -388,7 +388,7 @@ LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) LLMutexTrylock::~LLMutexTrylock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if (mMutex && mLocked) mMutex->unlock(); } @@ -400,7 +400,7 @@ LLMutexTrylock::~LLMutexTrylock() // LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if(mutex) { mutex->lock(); @@ -419,7 +419,7 @@ LLScopedLock::~LLScopedLock() void LLScopedLock::unlock() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if(mLocked) { mMutex->unlock(); diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index 722d9afca2..732436cc4f 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -84,7 +84,7 @@ extern thread_local bool gProfilerEnabled; // #define TRACY_NO_BROADCAST 1 // #define TRACY_ONLY_LOCALHOST 1 #define TRACY_ONLY_IPV4 1 - #include "Tracy.hpp" + #include "tracy/Tracy.hpp" // Enable OpenGL profiling #define LL_PROFILER_ENABLE_TRACY_OPENGL 0 diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 5e267c6805..2a935f7c04 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -475,7 +475,7 @@ LLSDNotationParser::~LLSDNotationParser() // virtual S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; // map: { string:object, string:object } // array: [ object, object, object ] // undef: ! @@ -735,7 +735,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; // map: { string:object, string:object } map = LLSD::emptyMap(); S32 parse_count = 0; @@ -796,7 +796,7 @@ S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) c S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; // array: [ object, object, object ] array = LLSD::emptyArray(); S32 parse_count = 0; @@ -836,7 +836,7 @@ S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_dept bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; std::string value; auto count = deserialize_string(istr, value, mMaxBytesLeft); if(PARSE_FAILURE == count) return false; @@ -847,7 +847,7 @@ bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; // binary: b##"ff3120ab1" // or: b(len)"..." diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index dd00c39180..6396caf8d5 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -930,7 +930,7 @@ void LLSDXMLParser::parsePart(const char *buf, llssize len) // virtual S32 LLSDXMLParser::doParse(std::istream& input, LLSD& data, S32 max_depth) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; #ifdef XML_PARSER_PERFORMANCE_TESTS XML_Timer timer( &parseTime ); diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 34aa0bc070..12f67208c1 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -214,7 +214,7 @@ bool compare_llsd_with_template( const LLSD& template_llsd, LLSD& resultant_llsd) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; if ( llsd_to_test.isUndefined() && @@ -337,7 +337,7 @@ bool filter_llsd_with_template( const LLSD & template_llsd, LLSD & resultant_llsd) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; if (llsd_to_test.isUndefined() && template_llsd.isDefined()) { @@ -533,7 +533,7 @@ class TypeLookup public: TypeLookup() { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) { @@ -543,7 +543,7 @@ public: std::string lookup(LLSD::Type type) const { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; MapType::const_iterator found = mMap.find(type); if (found != mMap.end()) @@ -595,7 +595,7 @@ static std::string match_types(LLSD::Type expect, // prototype.type() LLSD::Type actual, // type we're checking const std::string& pfx) // as for llsd_matches { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // Trivial case: if the actual type is exactly what we expect, we're good. if (actual == expect) @@ -634,7 +634,7 @@ static std::string match_types(LLSD::Type expect, // prototype.type() // see docstring in .h file std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // An undefined prototype means that any data is valid. // An undefined slot in an array or map prototype means that any data @@ -768,7 +768,7 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // We're comparing strict equality of LLSD representation rather than // performing any conversions. So if the types aren't equal, the LLSD @@ -878,7 +878,7 @@ namespace llsd LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // Treat rawPath uniformly as an array. If it's not already an array, // store it as the only entry in one. (But let's say Undefined means an @@ -905,7 +905,7 @@ LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) // path entry that's bad. for (LLSD::Integer i = 0; i < path.size(); ++i) { - LL_PROFILE_ZONE_NUM( i ) + LL_PROFILE_ZONE_NUM(i); const LLSD& key{path[i]}; if (key.isString()) @@ -935,7 +935,7 @@ LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) LLSD drill(const LLSD& blob, const LLSD& path) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; // drill_ref() does exactly what we want. Temporarily cast away // const-ness and use that. @@ -949,7 +949,7 @@ LLSD drill(const LLSD& blob, const LLSD& path) // filter may be include to exclude/include keys in a map. LLSD llsd_clone(LLSD value, LLSD filter) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; LLSD clone; bool has_filter(filter.isMap()); diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 8bee33be0a..79625ad9f8 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -929,7 +929,7 @@ LLSD LLMemoryInfo::getStatsMap() const LLMemoryInfo& LLMemoryInfo::refresh() { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; mStatsMap = loadStatsMap(); LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index faaaefd561..9eb92ca6a1 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -344,7 +344,7 @@ bool LLThread::runCondition(void) // Stop thread execution if requested until unpaused. void LLThread::checkPause() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mDataLock->lock(); // This is in a while loop because the pthread API allows for spurious wakeups. @@ -376,20 +376,20 @@ void LLThread::setQuitting() // static LLThread::id_t LLThread::currentID() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; return std::this_thread::get_id(); } // static void LLThread::yield() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; std::this_thread::yield(); } void LLThread::wake() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mDataLock->lock(); if(!shouldSleep()) { @@ -400,7 +400,7 @@ void LLThread::wake() void LLThread::wakeLocked() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; if(!shouldSleep()) { mRunCondition->signal(); @@ -409,13 +409,13 @@ void LLThread::wakeLocked() void LLThread::lockData() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mDataLock->lock(); } void LLThread::unlockData() { - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; mDataLock->unlock(); } diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index e5c0970d35..28d6e4e4cc 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -91,7 +91,7 @@ U32 micro_sleep(U64 us, U32 max_yields) U32 micro_sleep(U64 us, U32 max_yields) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; #if 0 LARGE_INTEGER ft; ft.QuadPart = -static_cast(us * 10); // '-' using relative time @@ -109,7 +109,7 @@ U32 micro_sleep(U64 us, U32 max_yields) void ms_sleep(U32 ms) { - LL_PROFILE_ZONE_SCOPED + LL_PROFILE_ZONE_SCOPED; micro_sleep(ms * 1000, 0); } -- cgit v1.2.3 From 8b59d9bb42f7a0421c27daa75672ae775bb8927f Mon Sep 17 00:00:00 2001 From: nat-goodspeed Date: Wed, 10 Jul 2024 11:15:51 -0400 Subject: Introduce LLCoros::RMutex, use for SettingsConfig::mRecorderMutex (#1980) in llerror.cpp. --- indra/llcommon/llcoros.h | 9 ++++++++- indra/llcommon/llerror.cpp | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 369d65407e..c3820ae987 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -31,8 +31,9 @@ #include "llexception.h" #include -#include #include +#include +#include #include "mutex.h" #include "llsingleton.h" #include "llinstancetracker.h" @@ -307,6 +308,12 @@ public: // use mutex, lock, condition_variable suitable for coroutines using Mutex = boost::fibers::mutex; + using RMutex = boost::fibers::recursive_mutex; + // With C++17, LockType is deprecated: at this point we can directly + // declare 'std::unique_lock lk(some_mutex)' without explicitly stating + // the mutex type. Sadly, making LockType an alias template for + // std::unique_lock doesn't work the same way: Class Template Argument + // Deduction only works for class templates, not alias templates. using LockType = std::unique_lock; using cv_status = boost::fibers::cv_status; using ConditionVariable = boost::fibers::condition_variable; diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index ff80863497..6c3b9c9542 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -55,7 +55,6 @@ #include "llsingleton.h" #include "llstl.h" #include "lltimer.h" -#include // On Mac, got: // #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define @@ -507,7 +506,7 @@ namespace LLError::TimeFunction mTimeFunction; Recorders mRecorders; - boost::fibers::recursive_mutex mRecorderMutex; + LLCoros::RMutex mRecorderMutex; int mShouldLogCallCounter; -- cgit v1.2.3 From 7679c76144057c85faabc196e0d189cebda75875 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Thu, 11 Jul 2024 12:23:17 -0500 Subject: Fix for tracy build. --- indra/llcommon/llsdserialize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 2a935f7c04..37af366a20 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -950,7 +950,7 @@ LLSDBinaryParser::~LLSDBinaryParser() // virtual S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD; /** * Undefined: '!'
* Boolean: '1' for true '0' for false
-- cgit v1.2.3 From ef9a494134458f3464b250c87da9e247d775989c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Sun, 14 Jul 2024 12:01:30 +0200 Subject: #1500 Better out of disk space handling --- indra/llcommon/llapp.cpp | 27 +++++++++++++++++++++++++++ indra/llcommon/llapp.h | 4 ++++ indra/llcommon/llapr.cpp | 7 ++++++- 3 files changed, 37 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index b85bd2573b..c2158bb5b8 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -487,6 +487,33 @@ int LLApp::getPid() #endif } +// static +void LLApp::notifyOutOfDiskSpace() +{ + static const U32Seconds min_interval = U32Seconds(60); + static U32Seconds min_time_to_send = U32Seconds(0); + U32Seconds now = LLTimer::getTotalTime(); + if (now < min_time_to_send) + return; + + min_time_to_send = now + min_interval; + + if (LLApp* app = instance()) + { + app->sendOutOfDiskSpaceNotification(); + } + else + { + LL_WARNS() << "No app instance" << LL_ENDL; + } +} + +// virtual +void LLApp::sendOutOfDiskSpaceNotification() +{ + LL_WARNS() << "Should never be called" << LL_ENDL; // Should be overridden +} + #ifndef LL_WINDOWS void setup_signals() { diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index ad8912ca88..d90ecdf661 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -202,6 +202,8 @@ public: static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) static int getPid(); + static void notifyOutOfDiskSpace(); + // // Sleep for specified time while still running // @@ -301,6 +303,8 @@ protected: */ void stepFrame(); + virtual void sendOutOfDiskSpaceNotification(); + private: // Contains the filename of the minidump file after a crash. char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH]; diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index b085f8f5dc..01763c49aa 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -28,6 +28,7 @@ #include "linden_common.h" #include "llapr.h" +#include "llapp.h" #include "llmutex.h" #include "apr_dso.h" @@ -606,7 +607,11 @@ S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, apr_status_t s = apr_file_write(file_handle, buf, &bytes_written); if (s != APR_SUCCESS) { - LL_WARNS("APR") << " Attempting to write filename: " << filename << LL_ENDL; + LL_WARNS("APR") << "Attempting to write filename: " << filename << LL_ENDL; + if (APR_STATUS_IS_ENOSPC(s)) + { + LLApp::notifyOutOfDiskSpace(); + } ll_apr_warn_status(s); bytes_written = 0; } -- cgit v1.2.3 From f0d7ccdbe0ccadb47847ca9ed2939c7c8ed06788 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 17 Jul 2024 22:16:20 +0200 Subject: #2053 BugSplat Crash #1494325: LLVOVolume::getReflectionProbeNearClip() --- indra/llcommon/llapp.cpp | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index c2158bb5b8..6da764f94c 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -90,7 +90,7 @@ bool LLApp::sDisableCrashlogger = false; // Local flag for whether or not to do logging in signal handlers. //static -bool LLApp::sLogInSignal = false; +bool LLApp::sLogInSignal = true; // static // Keeps track of application status @@ -373,6 +373,9 @@ static std::map statusDesc // static void LLApp::setStatus(EAppStatus status) { + auto status_it = statusDesc.find(status); + std::string status_text = status_it != statusDesc.end() ? std::string(status_it->second) : std::to_string(status); + LL_INFOS() << "status: " << status_text << LL_ENDL; // notify everyone waiting on sStatus any time its value changes sStatus.set_all(status); @@ -381,18 +384,7 @@ void LLApp::setStatus(EAppStatus status) if (! LLEventPumps::wasDeleted()) { // notify interested parties of status change - LLSD statsd; - auto found = statusDesc.find(status); - if (found != statusDesc.end()) - { - statsd = found->second; - } - else - { - // unknown status? at least report value - statsd = LLSD::Integer(status); - } - LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); + LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", status_text)); } } @@ -681,6 +673,7 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) { LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL; } + if (LLApp::isError()) { // Received second fatal signal while handling first, just die right now @@ -718,11 +711,11 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) clear_signals(); raise(signum); return; - } else { - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; - } + } + + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; } } } -- cgit v1.2.3 From 8020079eb2bb50175f72fc7dde38346db7ee2477 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 26 Jul 2024 15:54:36 +0200 Subject: Add missing semicolon --- indra/llcommon/llerror.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 6c3b9c9542..237f07288e 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -109,7 +109,7 @@ namespace { virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; int syslogPriority = LOG_CRIT; switch (level) { case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; -- cgit v1.2.3 From 3e322df4fb71cbeff27aab85bb48c7da595b548c Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Sun, 28 Jul 2024 16:27:42 -0400 Subject: Replace liburiparser with boost::url --- indra/llcommon/CMakeLists.txt | 2 - indra/llcommon/lluriparser.cpp | 168 ++++++++++++----------------------------- indra/llcommon/lluriparser.h | 27 ++++--- 3 files changed, 60 insertions(+), 137 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 95e991c246..8e43627a5f 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -10,7 +10,6 @@ include(Boost) include(LLSharedLibs) include(Copy3rdPartyLibs) include(ZLIBNG) -include(URIPARSER) include(Tracy) @@ -278,7 +277,6 @@ target_link_libraries( ll::expat ll::zlib-ng ll::boost - ll::uriparser ll::oslibraries ll::tracy ) diff --git a/indra/llcommon/lluriparser.cpp b/indra/llcommon/lluriparser.cpp index 2ebb7fc742..33a48d970d 100644 --- a/indra/llcommon/lluriparser.cpp +++ b/indra/llcommon/lluriparser.cpp @@ -29,12 +29,7 @@ #include "linden_common.h" #include "lluriparser.h" -#if LL_DARWIN -#include -#include -#endif - -LLUriParser::LLUriParser(const std::string& u) : mTmpScheme(false), mNormalizedTmp(false), mRes(0) +LLUriParser::LLUriParser(const std::string& u) : mTmpScheme(false), mNormalizedTmp(false), mRes(false) { if (u.find("://") == std::string::npos) { @@ -42,36 +37,52 @@ LLUriParser::LLUriParser(const std::string& u) : mTmpScheme(false), mNormalizedT mTmpScheme = true; } - mNormalizedUri += u.c_str(); + mNormalizedUri.append(u); mRes = parse(); } LLUriParser::~LLUriParser() { - uriFreeUriMembersA(&mUri); } -S32 LLUriParser::parse() +bool LLUriParser::parse() { - mRes = uriParseSingleUriA(&mUri, mNormalizedUri.c_str(), NULL); + try + { + auto res = boost::urls::parse_uri(mNormalizedUri); + if (res) + { + mUri = *res; + mRes = true; + } + else + { + mRes = false; + } + } + catch (const std::length_error&) + { + LL_WARNS() << "Failed to parse uri due to exceeding uri_view max_size" << LL_ENDL; + mRes = false; + } return mRes; } -const char * LLUriParser::scheme() const +const std::string& LLUriParser::scheme() const { - return mScheme.c_str(); + return mScheme; } -void LLUriParser::sheme(const std::string& s) +void LLUriParser::scheme(const std::string& s) { mTmpScheme = !s.size(); mScheme = s; } -const char * LLUriParser::port() const +const std::string& LLUriParser::port() const { - return mPort.c_str(); + return mPort; } void LLUriParser::port(const std::string& s) @@ -79,9 +90,9 @@ void LLUriParser::port(const std::string& s) mPort = s; } -const char * LLUriParser::host() const +const std::string& LLUriParser::host() const { - return mHost.c_str(); + return mHost; } void LLUriParser::host(const std::string& s) @@ -89,9 +100,9 @@ void LLUriParser::host(const std::string& s) mHost = s; } -const char * LLUriParser::path() const +const std::string& LLUriParser::path() const { - return mPath.c_str(); + return mPath; } void LLUriParser::path(const std::string& s) @@ -99,9 +110,9 @@ void LLUriParser::path(const std::string& s) mPath = s; } -const char * LLUriParser::query() const +const std::string& LLUriParser::query() const { - return mQuery.c_str(); + return mQuery; } void LLUriParser::query(const std::string& s) @@ -109,9 +120,9 @@ void LLUriParser::query(const std::string& s) mQuery = s; } -const char * LLUriParser::fragment() const +const std::string& LLUriParser::fragment() const { - return mFragment.c_str(); + return mFragment; } void LLUriParser::fragment(const std::string& s) @@ -119,19 +130,6 @@ void LLUriParser::fragment(const std::string& s) mFragment = s; } -void LLUriParser::textRangeToString(UriTextRangeA& textRange, std::string& str) -{ - if (textRange.first != NULL && textRange.afterLast != NULL && textRange.first < textRange.afterLast) - { - const ptrdiff_t len = textRange.afterLast - textRange.first; - str.assign(textRange.first, static_cast(len)); - } - else - { - str = LLStringUtil::null; - } -} - void LLUriParser::extractParts() { if (mTmpScheme || mNormalizedTmp) @@ -140,96 +138,24 @@ void LLUriParser::extractParts() } else { - textRangeToString(mUri.scheme, mScheme); + mScheme = mUri.scheme(); } - textRangeToString(mUri.hostText, mHost); - textRangeToString(mUri.portText, mPort); - textRangeToString(mUri.query, mQuery); - textRangeToString(mUri.fragment, mFragment); - - UriPathSegmentA * pathHead = mUri.pathHead; - while (pathHead) - { - std::string partOfPath; - textRangeToString(pathHead->text, partOfPath); - - mPath += '/'; - mPath += partOfPath; - - pathHead = pathHead->next; - } + mHost = mUri.host(); + mPort = mUri.port(); + mQuery = mUri.query(); + mFragment = mUri.fragment(); + mPath = mUri.path(); } -#if LL_DARWIN -typedef void(*sighandler_t)(int); -jmp_buf return_to_normalize; -static int sLastSignal = 0; -void uri_signal_handler(int signal) -{ - sLastSignal = signal; - // Apparently signal handler throwing an exception doesn't work. - // This is ugly and unsafe due to not unwinding content of uriparser library, - // but unless we have a way to catch this as NSexception, jump appears to be the only option. - longjmp(return_to_normalize, 1 /*setjmp will return this value*/); -} -#endif - -S32 LLUriParser::normalize() +bool LLUriParser::normalize() { mNormalizedTmp = mTmpScheme; - if (!mRes) + if (mRes) { -#if LL_DARWIN - sighandler_t last_sigill_handler, last_sigbus_handler; - last_sigill_handler = signal(SIGILL, &uri_signal_handler); // illegal instruction - last_sigbus_handler = signal(SIGBUS, &uri_signal_handler); - - if (setjmp(return_to_normalize)) - { - // Issue: external library crashed via signal - // If you encountered this, please try to figure out what's wrong: - // 1. Verify that library's input is 'sane' - // 2. Check if we have an NSexception to work with (unlikely) - // 3. See if passing same string causes exception to repeat - // - // Crash happens at uriNormalizeSyntaxExA - // Warning!!! This does not properly unwind stack, - // if this can be handled by NSexception, it needs to be remade - llassert(0); - - LL_WARNS() << "Uriparser crashed with " << sLastSignal << " , while processing: " << mNormalizedUri << LL_ENDL; - signal(SIGILL, last_sigill_handler); - signal(SIGBUS, last_sigbus_handler); - return 1; - } -#endif - - mRes = uriNormalizeSyntaxExA(&mUri, URI_NORMALIZE_SCHEME | URI_NORMALIZE_HOST); - -#if LL_DARWIN - signal(SIGILL, last_sigill_handler); - signal(SIGBUS, last_sigbus_handler); -#endif - - if (!mRes) - { - S32 chars_required; - mRes = uriToStringCharsRequiredA(&mUri, &chars_required); - - if (!mRes) - { - chars_required++; - std::vector label_buf(chars_required); - mRes = uriToStringA(&label_buf[0], &mUri, chars_required, NULL); - - if (!mRes) - { - mNormalizedUri = &label_buf[mTmpScheme ? 7 : 0]; - mTmpScheme = false; - } - } - } + mUri.normalize_scheme().normalize_authority(); + mNormalizedUri = mUri.buffer().substr(mTmpScheme ? 7 : 0); + mTmpScheme = false; } if(mTmpScheme && mNormalizedUri.size() > 7) @@ -302,7 +228,7 @@ bool LLUriParser::test() const return uri == mNormalizedUri; } -const char * LLUriParser::normalizedUri() const +const std::string& LLUriParser::normalizedUri() const { - return mNormalizedUri.c_str(); + return mNormalizedUri; } diff --git a/indra/llcommon/lluriparser.h b/indra/llcommon/lluriparser.h index 77eb4031d5..61d613f399 100644 --- a/indra/llcommon/lluriparser.h +++ b/indra/llcommon/lluriparser.h @@ -30,7 +30,7 @@ #define LL_LLURIPARSER_H #include -#include "uriparser/Uri.h" +#include "boost/url.hpp" class LL_COMMON_API LLUriParser { @@ -38,36 +38,35 @@ public: LLUriParser(const std::string& u); ~LLUriParser(); - const char * scheme() const; - void sheme (const std::string& s); + const std::string& scheme() const; + void scheme (const std::string& s); - const char * port() const; + const std::string& port() const; void port (const std::string& s); - const char * host() const; + const std::string& host() const; void host (const std::string& s); - const char * path() const; + const std::string& path() const; void path (const std::string& s); - const char * query() const; + const std::string& query() const; void query (const std::string& s); - const char * fragment() const; + const std::string& fragment() const; void fragment (const std::string& s); - const char * normalizedUri() const; + const std::string& normalizedUri() const; void extractParts(); void glue(std::string& uri) const; void glueFirst(std::string& uri, bool use_scheme = true) const; void glueSecond(std::string& uri) const; bool test() const; - S32 normalize(); + bool normalize(); private: - S32 parse(); - void textRangeToString(UriTextRangeA& textRange, std::string& str); + bool parse(); std::string mScheme; std::string mHost; std::string mPort; @@ -76,9 +75,9 @@ private: std::string mFragment; std::string mNormalizedUri; - UriUriA mUri; + boost::url mUri; - S32 mRes; + bool mRes; bool mTmpScheme; bool mNormalizedTmp; }; -- cgit v1.2.3 From 86668633c30c711b44dd5a16f98b9ac056f706b5 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Sun, 21 Jul 2024 19:18:13 -0400 Subject: Fix failure to join or detach threads causing rare shutdown termination --- indra/llcommon/llthread.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 9eb92ca6a1..e5d25b52f0 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -269,6 +269,7 @@ void LLThread::shutdown() mStatus = STOPPED; return; } + delete mThreadp; mThreadp = NULL; } @@ -299,6 +300,7 @@ void LLThread::start() { mThreadp = new std::thread(std::bind(&LLThread::threadRun, this)); mNativeHandle = mThreadp->native_handle(); + mThreadp->detach(); } catch (std::system_error& ex) { -- cgit v1.2.3 From 24854b4dd74edabf67efe533ef191553442a9bad Mon Sep 17 00:00:00 2001 From: Nicky Date: Sat, 3 Aug 2024 18:55:40 +0200 Subject: Covert gRandomGenerator into a unique_ptr. Having a static object of type LLRandFlagFib2281 needs a lot of space in the TLB, which is usually fine. Unless libcef gets loaded... CEF is compiled with static TLS/TLS model initial-exec and then having gRandomGenerator allocation so much space in the TLB will exhaust the available space and CEF cannot be loaded. --- indra/llcommon/llrand.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index 2c51e6f07f..513613f543 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,9 +58,26 @@ * to restore uniform distribution. */ -// gRandomGenerator is a stateful static object, which is therefore not +// pRandomGenerator is a stateful static object, which is therefore not // inherently thread-safe. -static thread_local LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); +//We use a pointer to not construct a huge object in the TLS space, sadly this is necessary +// due to libcef.so on Linux being compiled with TLS model initial-exec (resulting in +// FLAG STATIC_TLS, see readelf libcef.so). CEFs own TLS objects + LLRandLagFib2281 then will exhaust the +// available TLS space, causing media failure. + +static thread_local std::unique_ptr< LLRandLagFib2281 > pRandomGenerator = nullptr; + +namespace { + F64 ll_internal_get_rand() + { + if( !pRandomGenerator ) + { + pRandomGenerator.reset(new LLRandLagFib2281(LLUUID::getRandomSeed( ) )); + } + + return(*pRandomGenerator)(); + } +} // no default implementation, only specific F64 and F32 specializations template @@ -73,7 +90,7 @@ inline F64 ll_internal_random() // CPUs (or at least multi-threaded processes) seem to // occasionally give an obviously incorrect random number -- like // 5^15 or something. Sooooo, clamp it as described above. - F64 rv{ gRandomGenerator() }; + F64 rv{ ll_internal_get_rand() }; if(!((rv >= 0.0) && (rv < 1.0))) return fmod(rv, 1.0); return rv; } @@ -85,7 +102,7 @@ inline F32 ll_internal_random() // Per Monty, it's important to clamp using the correct fmodf() rather // than expanding to F64 for fmod() and then truncating back to F32. Prior // to this change, we were getting sporadic ll_frand() == 1.0 results. - F32 rv{ narrow(gRandomGenerator()) }; + F32 rv{ narrow(ll_internal_get_rand()) }; if(!((rv >= 0.0f) && (rv < 1.0f))) return fmodf(rv, 1.0f); return rv; } -- cgit v1.2.3 From fd15b4309b126fd5f83f2091c060a6043e18fdbf Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Tue, 6 Aug 2024 15:20:05 +0200 Subject: Ignore VmallocTotal as it's fixed, huge Number. (#2200) Not ignoring it will cause a lot of log spam due to conversion/parse errors. --- indra/llcommon/llsys.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 06b1855785..add8f18a62 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1107,6 +1107,14 @@ LLSD LLMemoryInfo::loadStatsMap() LLSD::String key(matched[1].first, matched[1].second); LLSD::String value_str(matched[2].first, matched[2].second); LLSD::Integer value(0); + + // Skip over VmallocTotal. It's just a fixed and huge number on (modern) systems. "34359738367 kB" + // https://unix.stackexchange.com/questions/700724/why-is-vmalloctotal-34359738367-kb + // If not skipped converting it to a LLSD::integer (32 bit) will fail and spam the logs (this function + // is called quite frequently). + if( key == "VmallocTotal") + continue; + try { value = boost::lexical_cast(value_str); -- cgit v1.2.3 From ae7f7cd6701ae9fed57c687b03aabad0d954b230 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 7 Aug 2024 09:28:06 +0300 Subject: Replace boost::shared_ptr with std in voice classes --- indra/llcommon/llerrorcontrol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index bf5a6df556..cbb703e9e7 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -198,7 +198,7 @@ namespace LLError }; /** - * @NOTE: addRecorder() and removeRecorder() uses the boost::shared_ptr to allow for shared ownership + * @NOTE: addRecorder() and removeRecorder() uses the std::shared_ptr to allow for shared ownership * while still ensuring that the allocated memory is eventually freed */ LL_COMMON_API void addRecorder(RecorderPtr); -- cgit v1.2.3 From 183b097072015fe83c751904d8133fa105717a5b Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Tue, 13 Aug 2024 16:28:23 -0400 Subject: Update tracy integration to 0.11 and rework client library to be configurable at build time Also copy tracy profiler client next to windows binary when enabled --- indra/llcommon/CMakeLists.txt | 4 ++++ indra/llcommon/llprofiler.cpp | 30 ++++++++++++++++++++++++++++++ indra/llcommon/llprofiler.h | 6 ------ 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 indra/llcommon/llprofiler.cpp (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 8e43627a5f..165bfd52d5 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -267,6 +267,10 @@ if (DARWIN) list(APPEND llcommon_SOURCE_FILES llsys_objc.mm) endif (DARWIN) +if (USE_TRACY) + list(APPEND llcommon_SOURCE_FILES llprofiler.cpp) +endif () + list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) add_library (llcommon ${llcommon_SOURCE_FILES}) diff --git a/indra/llcommon/llprofiler.cpp b/indra/llcommon/llprofiler.cpp new file mode 100644 index 0000000000..bdddabf977 --- /dev/null +++ b/indra/llcommon/llprofiler.cpp @@ -0,0 +1,30 @@ +/** +* @file llprofiler.cpp +* @brief Implementation of llprofiler +* @author Rye Cogtail +* +* $LicenseInfo:firstyear=2024&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2024, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "TracyClient.cpp" diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index 732436cc4f..b7d0522336 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -78,12 +78,6 @@ extern thread_local bool gProfilerEnabled; #if defined(LL_PROFILER_CONFIGURATION) && (LL_PROFILER_CONFIGURATION > LL_PROFILER_CONFIG_NONE) #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER - #define TRACY_ENABLE 1 -// Normally these would be enabled but we want to be able to build any viewer with Tracy enabled and run the Tracy server on another machine -// They must be undefined in order to work across multiple machines -// #define TRACY_NO_BROADCAST 1 -// #define TRACY_ONLY_LOCALHOST 1 - #define TRACY_ONLY_IPV4 1 #include "tracy/Tracy.hpp" // Enable OpenGL profiling -- cgit v1.2.3 From 70f455347eafa802036cf4a90d903d139a0fd7e9 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Tue, 13 Aug 2024 17:26:19 -0400 Subject: Introduce tracy instrumentation of mutex in LLSingleton, LLInstanceTracker and logging --- indra/llcommon/llerror.cpp | 55 ++++++++++++++++++-------------------- indra/llcommon/llinstancetracker.h | 22 ++++++++------- indra/llcommon/llprofiler.h | 18 +++++++++++++ indra/llcommon/llsingleton.cpp | 5 ++-- indra/llcommon/llsingleton.h | 18 +++++++------ 5 files changed, 69 insertions(+), 49 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 6c3b9c9542..41c69ba194 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -55,6 +55,7 @@ #include "llsingleton.h" #include "llstl.h" #include "lltimer.h" +#include "llprofiler.h" // On Mac, got: // #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define @@ -506,7 +507,7 @@ namespace LLError::TimeFunction mTimeFunction; Recorders mRecorders; - LLCoros::RMutex mRecorderMutex; + LL_PROFILE_MUTEX_NAMED(LLCoros::RMutex, mRecorderMutex, "Log Recorders"); int mShouldLogCallCounter; @@ -529,7 +530,6 @@ namespace mCrashFunction(NULL), mTimeFunction(NULL), mRecorders(), - mRecorderMutex(), mShouldLogCallCounter(0) { } @@ -1044,7 +1044,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); LL_PROFILE_MUTEX_LOCK(s->mRecorderMutex); s->mRecorders.push_back(recorder); } @@ -1055,7 +1055,7 @@ namespace LLError return; } SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); LL_PROFILE_MUTEX_LOCK(s->mRecorderMutex); s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), s->mRecorders.end()); } @@ -1104,7 +1104,7 @@ namespace LLError std::shared_ptr findRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); LL_PROFILE_MUTEX_LOCK(s->mRecorderMutex); return findRecorderPos(s).first; } @@ -1115,7 +1115,7 @@ namespace LLError bool removeRecorder() { SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); LL_PROFILE_MUTEX_LOCK(s->mRecorderMutex); auto found = findRecorderPos(s); if (found.first) { @@ -1221,7 +1221,7 @@ namespace std::string escaped_message; - std::unique_lock lock(s->mRecorderMutex); + std::unique_lock lock(s->mRecorderMutex); LL_PROFILE_MUTEX_LOCK(s->mRecorderMutex); for (LLError::RecorderPtr& r : s->mRecorders) { if (!r->enabled()) @@ -1280,24 +1280,21 @@ namespace } namespace { - // We need a couple different mutexes, but we want to use the same mechanism - // for both. Make getMutex() a template function with different instances - // for different MutexDiscriminator values. - enum MutexDiscriminator - { - LOG_MUTEX, - STACKS_MUTEX - }; // Some logging calls happen very early in processing -- so early that our // module-static variables aren't yet initialized. getMutex() wraps a // function-static LLMutex so that early calls can still have a valid // LLMutex instance. - template - LLMutex* getMutex() + auto getLogMutex() + { + // guaranteed to be initialized the first time control reaches here + static LL_PROFILE_MUTEX_NAMED(std::recursive_mutex, sLogMutex, "Log Mutex"); + return &sLogMutex; + } + auto getStacksMutex() { // guaranteed to be initialized the first time control reaches here - static LLMutex sMutex; - return &sMutex; + static LL_PROFILE_MUTEX_NAMED(std::recursive_mutex, sStacksMutex, "Stacks Mutex"); + return &sStacksMutex; } bool checkLevelMap(const LevelMap& map, const std::string& key, @@ -1347,8 +1344,8 @@ namespace LLError bool Log::shouldLog(CallSite& site) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) + std::unique_lock lock(*getLogMutex(), std::try_to_lock); LL_PROFILE_MUTEX_LOCK(*getLogMutex()); + if (!lock) { return false; } @@ -1392,8 +1389,8 @@ namespace LLError void Log::flush(const std::ostringstream& out, const CallSite& site) { LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING; - LLMutexTrylock lock(getMutex(),5); - if (!lock.isLocked()) + std::unique_lock lock(*getLogMutex(), std::try_to_lock); LL_PROFILE_MUTEX_LOCK(*getLogMutex()); + if (!lock) { return; } @@ -1523,8 +1520,8 @@ namespace LLError //static void LLCallStacks::push(const char* function, const int line) { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) + std::unique_lock lock(*getStacksMutex(), std::try_to_lock); LL_PROFILE_MUTEX_LOCK(*getStacksMutex()); + if (!lock) { return; } @@ -1548,8 +1545,8 @@ namespace LLError //static void LLCallStacks::end(const std::ostringstream& out) { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) + std::unique_lock lock(*getStacksMutex(), std::try_to_lock); LL_PROFILE_MUTEX_LOCK(*getStacksMutex()); + if (!lock) { return; } @@ -1565,8 +1562,8 @@ namespace LLError //static void LLCallStacks::print() { - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) + std::unique_lock lock(*getStacksMutex(), std::try_to_lock); LL_PROFILE_MUTEX_LOCK(*getStacksMutex()); + if (!lock) { return; } diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 3232a0e219..92b26354a1 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -52,7 +52,7 @@ namespace LLInstanceTrackerPrivate struct StaticBase { // We need to be able to lock static data while manipulating it. - std::mutex mMutex; + LL_PROFILE_MUTEX_NAMED(std::mutex, mMutex, "InstanceTracker Data"); }; void logerrs(const char* cls, const std::string&, const std::string&, const std::string&); @@ -101,7 +101,8 @@ public: static size_t instanceCount() { - return LockStatic()->mMap.size(); + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); + return lock->mMap.size(); } // snapshot of std::pair> pairs, for @@ -236,7 +237,7 @@ public: static ptr_t getInstance(const KEY& k) { - LockStatic lock; + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); const InstanceMap& map(lock->mMap); typename InstanceMap::const_iterator found = map.find(k); return (found == map.end()) ? NULL : found->second; @@ -252,19 +253,19 @@ protected: ptr_t ptr(static_cast(this), [](T*){}); // save corresponding weak_ptr for future reference mSelf = ptr; - LockStatic lock; + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); add_(lock, key, ptr); } public: virtual ~LLInstanceTracker() { - LockStatic lock; + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); remove_(lock); } protected: virtual void setKey(KEY key) { - LockStatic lock; + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); // Even though the shared_ptr we store in our map has a no-op deleter // for T itself, letting the use count decrement to 0 will still // delete the use-count object. Capture the shared_ptr we just removed @@ -376,7 +377,8 @@ public: static size_t instanceCount() { - return LockStatic()->mSet.size(); + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); + return lock->mSet.size(); } // snapshot of std::shared_ptr pointers @@ -488,14 +490,16 @@ protected: // save corresponding weak_ptr for future reference mSelf = ptr; // Also store it in our class-static set to track this instance. - LockStatic()->mSet.emplace(ptr); + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); + lock->mSet.emplace(ptr); } public: virtual ~LLInstanceTracker() { // convert weak_ptr to shared_ptr because that's what we store in our // InstanceSet - LockStatic()->mSet.erase(mSelf.lock()); + LockStatic lock; LL_PROFILE_MUTEX_LOCK(lock->mMutex); + lock->mSet.erase(mSelf.lock()); } protected: LLInstanceTracker(const LLInstanceTracker& other): diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index b7d0522336..f6a4d24747 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -102,6 +102,12 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red + + #define LL_PROFILE_MUTEX(type, varname) TracyLockable(type, varname) + #define LL_PROFILE_MUTEX_NAMED(type, varname, desc) TracyLockableN(type, varname, desc) + #define LL_PROFILE_MUTEX_SHARED(type, varname) TracySharedLockable(type, varname) + #define LL_PROFILE_MUTEX_SHARED_NAMED(type, varname, desc) TracySharedLockableN(type, varname, desc) + #define LL_PROFILE_MUTEX_LOCK(varname) { auto& mutex = varname; LockMark(mutex); } #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_FAST_TIMER #define LL_PROFILER_FRAME_END @@ -118,6 +124,12 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) (void)(name); // Not supported #define LL_PROFILE_ZONE_INFO(name) (void)(name); // Not supported #define LL_PROFILE_ZONE_WARN(name) (void)(name); // Not supported + + #define LL_PROFILE_MUTEX(type, varname) type varname + #define LL_PROFILE_MUTEX_NAMED(type, varname, desc) type varname + #define LL_PROFILE_MUTEX_SHARED(type, varname) type varname + #define LL_PROFILE_MUTEX_SHARED_NAMED(type, varname, desc) type varname + #define LL_PROFILE_MUTEX_LOCK(varname) // LL_PROFILE_MUTEX_LOCK is a no-op when Tracy is disabled #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER #define LL_PROFILER_FRAME_END FrameMark @@ -133,6 +145,12 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red + + #define LL_PROFILE_MUTEX(type, varname) TracyLockable(type, varname) + #define LL_PROFILE_MUTEX_NAMED(type, varname, desc) TracyLockableN(type, varname, desc) + #define LL_PROFILE_MUTEX_SHARED(type, varname) TracySharedLockable(type, varname) + #define LL_PROFILE_MUTEX_SHARED_NAMED(type, varname, desc) TracySharedLockableN(type, varname, desc) + #define LL_PROFILE_MUTEX_LOCK(varname) { auto& mutex = varname; LockMark(mutex); } // see https://github.com/wolfpld/tracy/issues/575 #endif #else #define LL_PROFILER_FRAME_END diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index d00e703a10..05dc3cde79 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -59,9 +59,8 @@ private: // it's safe to log -- which involves querying a different LLSingleton -- // which requires accessing the master list. typedef std::recursive_mutex mutex_t; - typedef std::unique_lock lock_t; - - mutex_t mMutex; + LL_PROFILE_MUTEX_NAMED(mutex_t, mMutex, "Singleton MasterList"); + typedef std::unique_lock lock_t; public: // Instantiate this to both obtain a reference to MasterList::instance() diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 316831cd74..b5659e053c 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -35,6 +35,8 @@ #include "lockstatic.h" #include "llthread.h" // on_main_thread() #include "llmainthreadtask.h" +#include "llprofiler.h" +#include "llerror.h" #ifdef LL_WINDOWS #pragma warning(push) @@ -298,7 +300,7 @@ private: // Use a recursive_mutex in case of constructor circularity. With a // non-recursive mutex, that would result in deadlock. typedef std::recursive_mutex mutex_t; - mutex_t mMutex; // LockStatic looks for mMutex + LL_PROFILE_MUTEX_NAMED(mutex_t, mMutex, "Singleton Data"); // LockStatic looks for mMutex EInitState mInitState{UNINITIALIZED}; DERIVED_TYPE* mInstance{nullptr}; @@ -420,7 +422,7 @@ protected: // deleteSingleton() to defend against manual deletion. When we moved // cleanup to deleteSingleton(), we hit crashes due to dangling // pointers in the MasterList. - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); lk->mInstance = nullptr; lk->mInitState = DELETED; @@ -448,7 +450,7 @@ public: // Hold the lock while we call cleanupSingleton() and the destructor. // Our destructor also instantiates LockStatic, requiring a recursive // mutex. - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); // of course, only cleanup and delete if there's something there if (lk->mInstance) { @@ -505,7 +507,7 @@ public: { // nested scope for 'lk' // In case racing threads call getInstance() at the same moment, // serialize the calls. - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); switch (lk->mInitState) { @@ -595,7 +597,7 @@ public: static bool instanceExists() { // defend any access to sData from racing threads - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); return lk->mInitState == INITIALIZED; } @@ -605,7 +607,7 @@ public: static bool wasDeleted() { // defend any access to sData from racing threads - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); return lk->mInitState == DELETED; } }; @@ -644,7 +646,7 @@ private: // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see // mInitState already set. - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); // For organizational purposes this function shouldn't be called twice if (lk->mInitState != super::UNINITIALIZED) { @@ -708,7 +710,7 @@ public: { // In case racing threads call getInstance() at the same moment as // initParamSingleton(), serialize the calls. - LockStatic lk; + LockStatic lk; LL_PROFILE_MUTEX_LOCK(lk->mMutex); switch (lk->mInitState) { -- cgit v1.2.3 From b5e306f7d89e82984a37824a3640bd67a5c45d61 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Wed, 14 Aug 2024 11:01:02 -0400 Subject: Enable /permissive- on MSVC for better standards conformance (#2251) * Enable /permissive- on MSVC for better C++ conformance and fix related errors * Clean up left over warning suppressions from old library or msvc versions --- indra/llcommon/llallocator_heap_profile.cpp | 7 ------- indra/llcommon/llevents.h | 10 +--------- indra/llcommon/llfindlocale.cpp | 2 +- indra/llcommon/llsdjson.cpp | 9 --------- indra/llcommon/llstacktrace.cpp | 3 --- indra/llcommon/llstring.cpp | 5 +++++ indra/llcommon/llstring.h | 5 +++-- indra/llcommon/lluuid.cpp | 1 + indra/llcommon/tests/commonmisc_test.cpp | 6 ------ 9 files changed, 11 insertions(+), 37 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llallocator_heap_profile.cpp b/indra/llcommon/llallocator_heap_profile.cpp index 85e56b4db4..8cc44b6a01 100644 --- a/indra/llcommon/llallocator_heap_profile.cpp +++ b/indra/llcommon/llallocator_heap_profile.cpp @@ -28,13 +28,6 @@ #include "linden_common.h" #include "llallocator_heap_profile.h" -#if LL_MSVC -// disable warning about boost::lexical_cast returning uninitialized data -// when it fails to parse the string -#pragma warning (disable:4701) -#pragma warning (disable:4702) -#endif - #include #include #include diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index f97fca0a32..4bf1fa07a2 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -38,16 +38,8 @@ #include #include #include -#if LL_WINDOWS - #pragma warning (push) - #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch - #pragma warning (disable : 4264) -#endif -#include -#if LL_WINDOWS - #pragma warning (pop) -#endif +#include #include #include // noncopyable #include diff --git a/indra/llcommon/llfindlocale.cpp b/indra/llcommon/llfindlocale.cpp index ac52f90c9f..b4bcc80ac4 100644 --- a/indra/llcommon/llfindlocale.cpp +++ b/indra/llcommon/llfindlocale.cpp @@ -185,7 +185,7 @@ canonise_fl(FL_Locale *l) { #define RML(pn,sn) MAKELANGID(LANG_##pn, SUBLANG_##sn) struct IDToCode { LANGID id; - char* code; + const char* code; }; static const IDToCode both_to_code[] = { {ML(ENGLISH,US), "en_US.ISO_8859-1"}, diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index e95d2e6c1c..5d38e55686 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -35,16 +35,7 @@ #include "llerror.h" #include "../llmath/llmath.h" -#if LL_WINDOWS -#pragma warning (push) -#pragma warning (disable : 4702) // compiler thinks unreachable code -#endif #include -#if LL_WINDOWS -#pragma warning (pop) -#endif - - //========================================================================= LLSD LlsdFromJson(const boost::json::value& val) diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index bda3579f60..ca8f4299d9 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -33,10 +33,7 @@ #include #include "llwin32headerslean.h" -#pragma warning (push) -#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. #include -#pragma warning (pop) typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( IN ULONG frames_to_skip, diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index c57f8b1e96..505789f9ea 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -900,6 +900,11 @@ void HeapFree_deleter(void* ptr) } // anonymous namespace +unsigned long windows_get_last_error() +{ + return GetLastError(); +} + template<> std::wstring windows_message(DWORD error) { diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index b69a068830..db716b1431 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -46,7 +46,6 @@ #endif #include -#include const char LL_UNKNOWN_CHAR = '?'; class LLSD; @@ -832,8 +831,10 @@ template<> LL_COMMON_API std::wstring windows_message(unsigned long error); /// Get Windows message string, implicitly calling GetLastError() +LL_COMMON_API unsigned long windows_get_last_error(); + template -STRING windows_message() { return windows_message(GetLastError()); } +STRING windows_message() { return windows_message(windows_get_last_error()); } //@} diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 3b37365ec7..7aeabc3c4a 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -31,6 +31,7 @@ // ugh, this is ugly. We need to straighten out our linking for this library #pragma comment(lib, "IPHLPAPI.lib") #include +#include #endif #include "llapp.h" diff --git a/indra/llcommon/tests/commonmisc_test.cpp b/indra/llcommon/tests/commonmisc_test.cpp index 0057a1f639..b1a284225e 100644 --- a/indra/llcommon/tests/commonmisc_test.cpp +++ b/indra/llcommon/tests/commonmisc_test.cpp @@ -46,12 +46,6 @@ #include "../test/lltut.h" - -#if LL_WINDOWS -// disable overflow warnings -#pragma warning(disable: 4307) -#endif - namespace tut { struct sd_data -- cgit v1.2.3 From 10ef293bc93230437d8aa3b17fcc1bfdc78f6ba8 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 15 Aug 2024 17:46:58 +0200 Subject: BugSplat Crash #1504587: std::vector::empty() --- indra/llcommon/lltracethreadrecorder.cpp | 45 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lltracethreadrecorder.cpp b/indra/llcommon/lltracethreadrecorder.cpp index be3e585ef8..375cb050cc 100644 --- a/indra/llcommon/lltracethreadrecorder.cpp +++ b/indra/llcommon/lltracethreadrecorder.cpp @@ -159,7 +159,8 @@ AccumulatorBufferGroup* ThreadRecorder::activate( AccumulatorBufferGroup* record ThreadRecorder::active_recording_list_t::iterator ThreadRecorder::bringUpToDate( AccumulatorBufferGroup* recording ) { #if LL_TRACE_ENABLED - if (mActiveRecordings.empty()) return mActiveRecordings.end(); + if (mActiveRecordings.empty()) + return mActiveRecordings.end(); mActiveRecordings.back()->mPartialRecording.sync(); BlockTimer::updateTimes(); @@ -202,7 +203,7 @@ ThreadRecorder::active_recording_list_t::iterator ThreadRecorder::bringUpToDate( #endif } -void ThreadRecorder::deactivate( AccumulatorBufferGroup* recording ) +void ThreadRecorder::deactivate(AccumulatorBufferGroup* recording) { #if LL_TRACE_ENABLED active_recording_list_t::iterator recording_it = bringUpToDate(recording); @@ -228,9 +229,10 @@ void ThreadRecorder::deactivate( AccumulatorBufferGroup* recording ) #endif } -ThreadRecorder::ActiveRecording::ActiveRecording( AccumulatorBufferGroup* target ) +ThreadRecorder::ActiveRecording::ActiveRecording(AccumulatorBufferGroup* target) : mTargetRecording(target) -{} +{ +} void ThreadRecorder::ActiveRecording::movePartialToTarget() { @@ -243,30 +245,30 @@ void ThreadRecorder::ActiveRecording::movePartialToTarget() // called by child thread -void ThreadRecorder::addChildRecorder( class ThreadRecorder* child ) +void ThreadRecorder::addChildRecorder(ThreadRecorder* child) { #if LL_TRACE_ENABLED - { LLMutexLock lock(&mChildListMutex); - mChildThreadRecorders.push_back(child); - } + LLMutexLock lock(&mChildListMutex); + mChildThreadRecorders.push_back(child); #endif } // called by child thread -void ThreadRecorder::removeChildRecorder( class ThreadRecorder* child ) +void ThreadRecorder::removeChildRecorder(ThreadRecorder* child) { #if LL_TRACE_ENABLED - { LLMutexLock lock(&mChildListMutex); - mChildThreadRecorders.remove(child); - } + LLMutexLock lock(&mChildListMutex); + mChildThreadRecorders.remove(child); #endif } void ThreadRecorder::pushToParent() { #if LL_TRACE_ENABLED - { LLMutexLock lock(&mSharedRecordingMutex); - LLTrace::get_thread_recorder()->bringUpToDate(&mThreadRecordingBuffers); + if (ThreadRecorder* recorder = LLTrace::get_thread_recorder()) + { + LLMutexLock lock(&mSharedRecordingMutex); + recorder->bringUpToDate(&mThreadRecordingBuffers); mSharedRecordingBuffers.append(mThreadRecordingBuffers); mThreadRecordingBuffers.reset(); } @@ -278,15 +280,14 @@ void ThreadRecorder::pullFromChildren() { #if LL_TRACE_ENABLED LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; - if (mActiveRecordings.empty()) return; - - { LLMutexLock lock(&mChildListMutex); - + if (!mActiveRecordings.empty()) + { + LLMutexLock lock(&mChildListMutex); AccumulatorBufferGroup& target_recording_buffers = mActiveRecordings.back()->mPartialRecording; target_recording_buffers.sync(); for (LLTrace::ThreadRecorder* rec : mChildThreadRecorders) - { LLMutexLock lock(&(rec->mSharedRecordingMutex)); - + { + LLMutexLock lock(&(rec->mSharedRecordingMutex)); target_recording_buffers.merge(rec->mSharedRecordingBuffers); rec->mSharedRecordingBuffers.reset(); } @@ -294,13 +295,11 @@ void ThreadRecorder::pullFromChildren() #endif } - -void set_master_thread_recorder( ThreadRecorder* recorder ) +void set_master_thread_recorder(ThreadRecorder* recorder) { sMasterThreadRecorder = recorder; } - ThreadRecorder* get_master_thread_recorder() { return sMasterThreadRecorder; -- cgit v1.2.3 From d6190bbf13547800c56828d7670944f1a06af7dd Mon Sep 17 00:00:00 2001 From: Ansariel Hiller Date: Mon, 19 Aug 2024 08:39:35 +0200 Subject: Refactor LLFileSystem for and fix an old issue in LLFile (#2332) --- indra/llcommon/llfile.cpp | 4 ++-- indra/llcommon/llfile.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 9045324bf2..ed94ef21ef 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -293,7 +293,7 @@ int LLFile::rename(const std::string& filename, const std::string& newname, int return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error); } -bool LLFile::copy(const std::string from, const std::string to) +bool LLFile::copy(const std::string& from, const std::string& to) { bool copied = false; LLFILE* in = LLFile::fopen(from, "rb"); /* Flawfinder: ignore */ @@ -424,7 +424,7 @@ LLFILE * LLFile::_Fiopen(const std::string& filename, if (valid[n] == 0) return (0); // no valid mode - else if (norepflag && mode & (ios_base::out || ios_base::app) + else if (norepflag && mode & (ios_base::out | ios_base::app) && (fp = LLFile::fopen(filename, "r")) != 0) /* Flawfinder: ignore */ { // file must not exist, close and fail fclose(fp); diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 74110343fc..1661cbeb55 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -77,7 +77,7 @@ public: static int rmdir(const std::string& filename); static int remove(const std::string& filename, int supress_error = 0); static int rename(const std::string& filename,const std::string& newname, int supress_error = 0); - static bool copy(const std::string from, const std::string to); + static bool copy(const std::string& from, const std::string& to); static int stat(const std::string& filename,llstat* file_status); static bool isdir(const std::string& filename); @@ -160,7 +160,7 @@ private: * Does The Right Thing when passed a non-ASCII pathname. Sadly, that isn't * true of Microsoft's std::ifstream. */ -class LL_COMMON_API llifstream : public std::ifstream +class LL_COMMON_API llifstream : public std::ifstream { // input stream associated with a C stream public: @@ -205,7 +205,7 @@ class LL_COMMON_API llifstream : public std::ifstream * Right Thing when passed a non-ASCII pathname. Sadly, that isn't true of * Microsoft's std::ofstream. */ -class LL_COMMON_API llofstream : public std::ofstream +class LL_COMMON_API llofstream : public std::ofstream { public: // Constructors: @@ -241,7 +241,7 @@ class LL_COMMON_API llofstream : public std::ofstream /** - * @breif filesize helpers. + * @brief filesize helpers. * * The file size helpers are not considered particularly efficient, * and should only be used for config files and the like -- not in a -- cgit v1.2.3 From 862a4671f2a48eae4a2360d570b71f4adbb7328c Mon Sep 17 00:00:00 2001 From: Ansariel Hiller Date: Mon, 19 Aug 2024 23:47:42 +0200 Subject: Remove orphaned LLAllocator (#2348) --- indra/llcommon/CMakeLists.txt | 4 - indra/llcommon/llallocator.cpp | 58 ------------ indra/llcommon/llallocator.h | 51 ---------- indra/llcommon/llallocator_heap_profile.cpp | 138 ---------------------------- indra/llcommon/llallocator_heap_profile.h | 71 -------------- 5 files changed, 322 deletions(-) delete mode 100644 indra/llcommon/llallocator.cpp delete mode 100644 indra/llcommon/llallocator.h delete mode 100644 indra/llcommon/llallocator_heap_profile.cpp delete mode 100644 indra/llcommon/llallocator_heap_profile.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 165bfd52d5..c4041f0c79 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -18,8 +18,6 @@ set(llcommon_SOURCE_FILES commoncontrol.cpp indra_constants.cpp lazyeventapi.cpp - llallocator.cpp - llallocator_heap_profile.cpp llapp.cpp llapr.cpp llassettype.cpp @@ -127,8 +125,6 @@ set(llcommon_HEADER_FILES lazyeventapi.h linden_common.h llalignedarray.h - llallocator.h - llallocator_heap_profile.h llapp.h llapr.h llassettype.h diff --git a/indra/llcommon/llallocator.cpp b/indra/llcommon/llallocator.cpp deleted file mode 100644 index abe3779b85..0000000000 --- a/indra/llcommon/llallocator.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file llallocator.cpp - * @brief Implementation of the LLAllocator class. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llallocator.h" - -// -// stub implementations for when tcmalloc is disabled -// - -void LLAllocator::setProfilingEnabled(bool should_enable) -{ -} - -// static -bool LLAllocator::isProfiling() -{ - return false; -} - -std::string LLAllocator::getRawProfile() -{ - return std::string(); -} - -LLAllocatorHeapProfile const & LLAllocator::getProfile() -{ - mProf.mLines.clear(); - - // *TODO - avoid making all these extra copies of things... - std::string prof_text = getRawProfile(); - //std::cout << prof_text << std::endl; - mProf.parse(prof_text); - return mProf; -} diff --git a/indra/llcommon/llallocator.h b/indra/llcommon/llallocator.h deleted file mode 100644 index aa3eead546..0000000000 --- a/indra/llcommon/llallocator.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @file llallocator.h - * @brief Declaration of the LLAllocator class. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLALLOCATOR_H -#define LL_LLALLOCATOR_H - -#include - -#include "llallocator_heap_profile.h" - -class LL_COMMON_API LLAllocator { - friend class LLMemoryView; - -public: - void setProfilingEnabled(bool should_enable); - - static bool isProfiling(); - - LLAllocatorHeapProfile const & getProfile(); - -private: - std::string getRawProfile(); - -private: - LLAllocatorHeapProfile mProf; -}; - -#endif // LL_LLALLOCATOR_H diff --git a/indra/llcommon/llallocator_heap_profile.cpp b/indra/llcommon/llallocator_heap_profile.cpp deleted file mode 100644 index 8cc44b6a01..0000000000 --- a/indra/llcommon/llallocator_heap_profile.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @file llallocator_heap_profile.cpp - * @brief Implementation of the parser for tcmalloc heap profile data. - * @author Brad Kittenbrink - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "llallocator_heap_profile.h" - -#include -#include -#include -#include - -static const std::string HEAP_PROFILE_MAGIC_STR = "heap profile:"; - -static bool is_separator(char c) -{ - return isspace(c) || c == '[' || c == ']' || c == ':'; -} - -void LLAllocatorHeapProfile::parse(std::string const & prof_text) -{ - // a typedef for handling a token in the string buffer - // it's a begin/end pair of string::const_iterators - typedef boost::iterator_range range_t; - - mLines.clear(); - - if(prof_text.compare(0, HEAP_PROFILE_MAGIC_STR.length(), HEAP_PROFILE_MAGIC_STR) != 0) - { - // *TODO - determine if there should be some better error state than - // mLines being empty. -brad - LL_WARNS() << "invalid heap profile data passed into parser." << LL_ENDL; - return; - } - - std::vector< range_t > prof_lines; - - std::string::const_iterator prof_begin = prof_text.begin() + HEAP_PROFILE_MAGIC_STR.length(); - - range_t prof_range(prof_begin, prof_text.end()); - boost::algorithm::split(prof_lines, - prof_range, - boost::bind(std::equal_to(), '\n', _1)); - - std::vector< range_t >::const_iterator i; - for(i = prof_lines.begin(); i != prof_lines.end() && !i->empty(); ++i) - { - range_t const & line_text = *i; - - std::vector line_elems; - - boost::algorithm::split(line_elems, - line_text, - is_separator); - - std::vector< range_t >::iterator j; - j = line_elems.begin(); - - while(j != line_elems.end() && j->empty()) { ++j; } // skip any separator tokens - llassert_always(j != line_elems.end()); - U32 live_count = boost::lexical_cast(*j); - ++j; - - while(j != line_elems.end() && j->empty()) { ++j; } // skip any separator tokens - llassert_always(j != line_elems.end()); - U64 live_size = boost::lexical_cast(*j); - ++j; - - while(j != line_elems.end() && j->empty()) { ++j; } // skip any separator tokens - llassert_always(j != line_elems.end()); - U32 tot_count = boost::lexical_cast(*j); - ++j; - - while(j != line_elems.end() && j->empty()) { ++j; } // skip any separator tokens - llassert_always(j != line_elems.end()); - U64 tot_size = boost::lexical_cast(*j); - ++j; - - while(j != line_elems.end() && j->empty()) { ++j; } // skip any separator tokens - llassert(j != line_elems.end()); - if (j != line_elems.end()) - { - ++j; // skip the '@' - - mLines.push_back(line(live_count, live_size, tot_count, tot_size)); - line & current_line = mLines.back(); - - for(; j != line_elems.end(); ++j) - { - if(!j->empty()) - { - U32 marker = boost::lexical_cast(*j); - current_line.mTrace.push_back(marker); - } - } - } - } - // *TODO - parse MAPPED_LIBRARIES section here if we're ever interested in it -} - -void LLAllocatorHeapProfile::dump(std::ostream & out) const -{ - for (const LLAllocatorHeapProfile::line& line : mLines) - { - out << line.mLiveCount << ": " << line.mLiveSize << '[' << line.mTotalCount << ": " << line.mTotalSize << "] @"; - - for (const stack_marker marker : line.mTrace) - { - out << ' ' << marker; - } - out << '\n'; - } - out.flush(); -} - diff --git a/indra/llcommon/llallocator_heap_profile.h b/indra/llcommon/llallocator_heap_profile.h deleted file mode 100644 index 22f284b703..0000000000 --- a/indra/llcommon/llallocator_heap_profile.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @file llallocator_heap_profile.h - * @brief Declaration of the parser for tcmalloc heap profile data. - * @author Brad Kittenbrink - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLALLOCATOR_HEAP_PROFILE_H -#define LL_LLALLOCATOR_HEAP_PROFILE_H - -#include "stdtypes.h" - -#include -#include - -class LLAllocatorHeapProfile -{ -public: - typedef int stack_marker; - - typedef std::vector stack_trace; - - struct line { - line(U32 live_count, U64 live_size, U32 tot_count, U64 tot_size) : - mLiveSize(live_size), - mTotalSize(tot_size), - mLiveCount(live_count), - mTotalCount(tot_count) - { - } - U64 mLiveSize, mTotalSize; - U32 mLiveCount, mTotalCount; - stack_trace mTrace; - }; - - typedef std::vector lines_t; - - LLAllocatorHeapProfile() - { - } - - void parse(std::string const & prof_text); - - void dump(std::ostream & out) const; - -public: - lines_t mLines; -}; - - -#endif // LL_LLALLOCATOR_HEAP_PROFILE_H -- cgit v1.2.3 From c4e1b801b639c5408ec6e6aae615a4bf1e673efe Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 20 Aug 2024 06:30:12 +0300 Subject: Post-merge build fix (#2059) --- indra/llcommon/llexception.cpp | 3 --- indra/llcommon/llexception.h | 7 +++++-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp index 107fdc2b2d..0a2f2ab607 100644 --- a/indra/llcommon/llexception.cpp +++ b/indra/llcommon/llexception.cpp @@ -18,9 +18,6 @@ #include #include #include -#if LL_WINDOWS -#include -#endif // LL_WINDOWS // external library headers #include #include diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index f58a553eb3..2a8c18507a 100644 --- a/indra/llcommon/llexception.h +++ b/indra/llcommon/llexception.h @@ -17,6 +17,9 @@ #include #include #include +#if LL_WINDOWS +#include +#endif // LL_WINDOWS // "Found someone who can comfort me // But there are always exceptions..." @@ -180,6 +183,8 @@ auto catcher(TRYCODE&& trycode, HANDLER&& handler) std::forward(handler)); } +[[noreturn]] void rethrow(U32 code, const std::string& stacktrace); + // monadic variant specifies try(), assumes default filter and handler template auto catcher(TRYCODE&& trycode) @@ -187,8 +192,6 @@ auto catcher(TRYCODE&& trycode) return catcher(std::forward(trycode), rethrow); } -[[noreturn]] void rethrow(U32 code, const std::string& stacktrace); - #else // not LL_WINDOWS ----------------------------------------------------- template -- cgit v1.2.3 From 9f7dd0177201fe080c287144b99a70125be1fb2b Mon Sep 17 00:00:00 2001 From: Ansariel Hiller Date: Tue, 20 Aug 2024 17:41:48 +0200 Subject: Clean up boost includes and remove compiler warning pragma for unreachable code in PCH (#2361) --- indra/llcommon/lldoubledispatch.h | 1 - indra/llcommon/llerrorcontrol.h | 1 - indra/llcommon/llinitparam.h | 1 - indra/llcommon/llprocess.h | 1 - indra/llcommon/llrun.h | 1 - indra/llcommon/tests/lleventcoro_test.cpp | 2 -- indra/llcommon/tests/llinstancetracker_test.cpp | 2 -- 7 files changed, 9 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lldoubledispatch.h b/indra/llcommon/lldoubledispatch.h index c8c566205a..25039c3e9c 100644 --- a/indra/llcommon/lldoubledispatch.h +++ b/indra/llcommon/lldoubledispatch.h @@ -30,7 +30,6 @@ #define LL_LLDOUBLEDISPATCH_H #include -#include #include #include #include diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index 1845fc42db..0a7b3d2046 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -32,7 +32,6 @@ #include "llpointer.h" #include "llrefcount.h" #include "boost/function.hpp" -#include "boost/shared_ptr.hpp" #include class LLSD; diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index 206aa51ba3..32d7b17034 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 166da8f424..39ed29c1b4 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -32,7 +32,6 @@ #include "llwin32headerslean.h" #include "llexception.h" #include "apr_thread_proc.h" -#include #include #include #include diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index 8061117ad5..70767572ff 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -30,7 +30,6 @@ #define LL_LLRUN_H #include -#include class LLRunnable; diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index a3c54ffaa2..ab174a8bde 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -30,8 +30,6 @@ #include #include #include -#include -#include #include "linden_common.h" diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp index c6eb0fdf75..bf661dc051 100644 --- a/indra/llcommon/tests/llinstancetracker_test.cpp +++ b/indra/llcommon/tests/llinstancetracker_test.cpp @@ -37,8 +37,6 @@ #include // std::sort() #include // std headers -// external library headers -#include // other Linden headers #include "../test/lltut.h" -- cgit v1.2.3 From 0fd5efbb2bd5f50dcb7228676bc4ce5c288da8e5 Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 20 Aug 2024 20:11:49 +0200 Subject: include for dstd::unique_ptr template --- indra/llcommon/llapr.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 00ff4d60b7..faf2d6fc92 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -33,6 +33,7 @@ #include // Need PATH_MAX in APR headers... #endif +#include #include #include "llwin32headerslean.h" #include "apr_thread_proc.h" -- cgit v1.2.3 From ef916bd3ae39fb6fd718428aa2a695bbea891940 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Thu, 22 Aug 2024 00:43:50 -0400 Subject: Remove or suppress left over debug messages in hot paths (#2392) --- indra/llcommon/llcallstack.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcallstack.h b/indra/llcommon/llcallstack.h index ad10a9dbf7..33f2b507b8 100644 --- a/indra/llcommon/llcallstack.h +++ b/indra/llcommon/llcallstack.h @@ -79,9 +79,13 @@ struct LLContextStatus LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status); +#ifndef LL_RELEASE_FOR_DOWNLOAD #define dumpStack(tag) \ LL_DEBUGS(tag) << "STACK:\n" \ << "====================\n" \ << LLCallStack() \ << "====================" \ << LL_ENDL; +#else +#define dumpStack(tag) +#endif -- cgit v1.2.3 From 316f0f0b8cb05515068e2acb72740fbab2365ce0 Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Thu, 22 Aug 2024 09:11:28 -0700 Subject: secondlife/viewer#2391: Remove avatar rigging "callstack" logging --- indra/llcommon/CMakeLists.txt | 2 - indra/llcommon/llcallstack.cpp | 188 ----------------------------------------- indra/llcommon/llcallstack.h | 91 -------------------- 3 files changed, 281 deletions(-) delete mode 100644 indra/llcommon/llcallstack.cpp delete mode 100644 indra/llcommon/llcallstack.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index c4041f0c79..437b8d0168 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -26,7 +26,6 @@ set(llcommon_SOURCE_FILES llbase64.cpp llbitpack.cpp llcallbacklist.cpp - llcallstack.cpp llcleanup.cpp llcommon.cpp llcommonutils.cpp @@ -134,7 +133,6 @@ set(llcommon_HEADER_FILES llbitpack.h llboost.h llcallbacklist.h - llcallstack.h llcleanup.h llcommon.h llcommonutils.h diff --git a/indra/llcommon/llcallstack.cpp b/indra/llcommon/llcallstack.cpp deleted file mode 100644 index c0be4f598e..0000000000 --- a/indra/llcommon/llcallstack.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @file llcallstack.cpp - * @brief run-time extraction of the current callstack - * - * $LicenseInfo:firstyear=2016&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2016, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llcommon.h" -#include "llcallstack.h" -#include "StackWalker.h" -#include "llthreadlocalstorage.h" - -#if LL_WINDOWS -class LLCallStackImpl: public StackWalker -{ -public: - LLCallStackImpl(): - StackWalker(false,0) // non-verbose, options = 0 - { - } - ~LLCallStackImpl() - { - } - void getStack(std::vector& stack, S32 skip_count=0, bool verbose=false) - { - m_stack.clear(); - ShowCallstack(verbose); - // Skip the first few lines because they're just bookkeeping for LLCallStack, - // plus any additional lines requested to skip. - S32 first_line = skip_count + 3; - for (S32 i=first_line; i m_stack; -}; -#else -// Stub - not implemented currently on other platforms. -class LLCallStackImpl -{ -public: - LLCallStackImpl() {} - ~LLCallStackImpl() {} - void getStack(std::vector& stack, S32 skip_count=0, bool verbose=false) - { - stack.clear(); - } -}; -#endif - -LLCallStackImpl *LLCallStack::s_impl = NULL; - -LLCallStack::LLCallStack(S32 skip_count, bool verbose): - m_skipCount(skip_count), - m_verbose(verbose) -{ - if (!s_impl) - { - s_impl = new LLCallStackImpl; - } - LLTimer t; - s_impl->getStack(m_strings, m_skipCount, m_verbose); -} - -bool LLCallStack::contains(const std::string& str) -{ - for (const std::string& src_str : m_strings) - { - if (src_str.find(str) != std::string::npos) - { - return true; - } - } - return false; -} - -std::ostream& operator<<(std::ostream& s, const LLCallStack& call_stack) -{ -#ifndef LL_RELEASE_FOR_DOWNLOAD - for (const std::string& str : call_stack.m_strings) - { - s << str; - } -#else - s << "UNAVAILABLE IN RELEASE"; -#endif - return s; -} - -LLContextStrings::LLContextStrings() -{ -} - -// static -LLContextStrings* LLContextStrings::getThreadLocalInstance() -{ - LLContextStrings *cons = LLThreadLocalSingletonPointer::getInstance(); - if (!cons) - { - LLThreadLocalSingletonPointer::setInstance(new LLContextStrings); - } - return LLThreadLocalSingletonPointer::getInstance(); -} - -// static -void LLContextStrings::addContextString(const std::string& str) -{ - LLContextStrings *cons = getThreadLocalInstance(); - //LL_INFOS() << "CTX " << (S32)cons << " ADD " << str << " CNT " << cons->m_contextStrings[str] << LL_ENDL; - cons->m_contextStrings[str]++; -} - -// static -void LLContextStrings::removeContextString(const std::string& str) -{ - LLContextStrings *cons = getThreadLocalInstance(); - cons->m_contextStrings[str]--; - //LL_INFOS() << "CTX " << (S32)cons << " REMOVE " << str << " CNT " << cons->m_contextStrings[str] << LL_ENDL; - if (cons->m_contextStrings[str] == 0) - { - cons->m_contextStrings.erase(str); - } -} - -// static -bool LLContextStrings::contains(const std::string& str) -{ - const std::map& strings = - LLThreadLocalSingletonPointer::getInstance()->m_contextStrings; - for (const std::map::value_type& str_pair : strings) - { - if (str_pair.first.find(str) != std::string::npos) - { - return true; - } - } - return false; -} - -// static -void LLContextStrings::output(std::ostream& os) -{ - const std::map& strings = - LLThreadLocalSingletonPointer::getInstance()->m_contextStrings; - for (const std::map::value_type& str_pair : strings) - { - os << str_pair.first << "[" << str_pair.second << "]" << "\n"; - } -} - -// static -std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status) -{ - LLThreadLocalSingletonPointer::getInstance()->output(s); - return s; -} - -bool LLContextStatus::contains(const std::string& str) -{ - return LLThreadLocalSingletonPointer::getInstance()->contains(str); -} diff --git a/indra/llcommon/llcallstack.h b/indra/llcommon/llcallstack.h deleted file mode 100644 index 33f2b507b8..0000000000 --- a/indra/llcommon/llcallstack.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @file llcallstack.h - * @brief run-time extraction of the current callstack - * - * $LicenseInfo:firstyear=2016&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2016, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include - -class LLCallStackImpl; - -class LLCallStack -{ -public: - LLCallStack(S32 skip_count=0, bool verbose=false); - std::vector m_strings; - bool m_verbose; - bool contains(const std::string& str); -private: - static LLCallStackImpl *s_impl; - S32 m_skipCount; -}; - -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCallStack& call_stack); - -class LLContextStrings -{ -public: - LLContextStrings(); - static void addContextString(const std::string& str); - static void removeContextString(const std::string& str); - static void output(std::ostream& os); - static LLContextStrings* getThreadLocalInstance(); - static bool contains(const std::string& str); -private: - std::map m_contextStrings; -}; - -class LLScopedContextString -{ -public: - LLScopedContextString(const std::string& str): - m_str(str) - { - LLContextStrings::addContextString(m_str); - } - ~LLScopedContextString() - { - LLContextStrings::removeContextString(m_str); - } -private: - std::string m_str; -}; - -// Mostly exists as a class to hook an ostream override to. -struct LLContextStatus -{ - bool contains(const std::string& str); -}; - -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status); - -#ifndef LL_RELEASE_FOR_DOWNLOAD -#define dumpStack(tag) \ - LL_DEBUGS(tag) << "STACK:\n" \ - << "====================\n" \ - << LLCallStack() \ - << "====================" \ - << LL_ENDL; -#else -#define dumpStack(tag) -#endif -- cgit v1.2.3 From 367c8f2363cebbcd6c37484d15e0863612bcc1c9 Mon Sep 17 00:00:00 2001 From: Brad Linden <46733234+brad-linden@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:24:07 -0700 Subject: Improve safety of macOS available RAM calcuations at suggestion of Ansariel and Beq (#2423) --- indra/llcommon/llmemory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 99c803e46f..ba48319a16 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -129,7 +129,7 @@ void LLMemory::updateMemoryInfo() { // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of // memory that the memory manager has committed for a running process", which is rss. - sAllocatedPageSizeInKB = U32Bytes(info.resident_size); + sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(info.resident_size)); // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. @@ -139,7 +139,7 @@ void LLMemory::updateMemoryInfo() // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. // // (On Windows, we use WorkingSetSize.) - sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); + sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(info.resident_size - info.reusable)); } else { -- cgit v1.2.3 From f15228023f3a1094f69891a5f431cff3d895515a Mon Sep 17 00:00:00 2001 From: Brad Linden <46733234+brad-linden@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:20:25 -0700 Subject: Attempt to get better file/line info for LL_ERRS crahses in bugsplat. (#2447) secondlife/viewer#2445 --- indra/llcommon/llerror.cpp | 16 ---------------- indra/llcommon/llerror.h | 11 +++++------ 2 files changed, 5 insertions(+), 22 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 41c69ba194..ad35bc84f2 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1641,19 +1641,3 @@ namespace LLError sLocalizedOutOfMemoryWarning = message; } } - -void crashdriver(void (*callback)(int*)) -{ - // The LLERROR_CRASH macro used to have inline code of the form: - //int* make_me_crash = NULL; - //*make_me_crash = 0; - - // But compilers are getting smart enough to recognize that, so we must - // assign to an address supplied by a separate source file. We could do - // the assignment here in crashdriver() -- but then BugSplat would group - // all LL_ERRS() crashes as the fault of this one function, instead of - // identifying the specific LL_ERRS() source line. So instead, do the - // assignment in a lambda in the caller's source. We just provide the - // nullptr target. - callback(nullptr); -} diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 6176ce0d1d..8a143ff30a 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -408,9 +408,11 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; #define LL_NEWLINE '\n' // Use this only in LL_ERRS or in a place that LL_ERRS may not be used -#define LLERROR_CRASH \ -{ \ - crashdriver([](int* ptr){ *ptr = 0; exit(*ptr); }); \ +#define LLERROR_CRASH \ +{ \ + int* make_me_crash = (int*)0xDEADBEEFDEADBEEFUL; \ + *make_me_crash = 0; \ + exit(*make_me_crash); \ } #define LL_ENDL \ @@ -512,7 +514,4 @@ LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches the result in a static variable. */ -// used by LLERROR_CRASH -void crashdriver(void (*)(int*)); - #endif // LL_LLERROR_H -- cgit v1.2.3