summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorcoyot@coyot-sager-PC <coyot@coyot-sager-PC>2017-02-28 22:56:30 +0000
committercoyot@coyot-sager-PC <coyot@coyot-sager-PC>2017-02-28 22:56:30 +0000
commit3a3da6e4a76859f32dc36491fde2992e92ae57b5 (patch)
treef61a7417ad6ffcb8488c910cfa64f14afe979071 /indra/llcommon
parent069c938eb6ebfd77f6a415207331c66f72270e5f (diff)
parentf0b256b1cb6c96aed81ee456e505247fd2169c5f (diff)
merge
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt35
-rw-r--r--indra/llcommon/StackWalker.cpp1388
-rw-r--r--indra/llcommon/StackWalker.h226
-rw-r--r--indra/llcommon/llallocator.cpp43
-rw-r--r--indra/llcommon/llapp.cpp3
-rw-r--r--indra/llcommon/llapp.h8
-rw-r--r--indra/llcommon/llapr.cpp2
-rw-r--r--indra/llcommon/llassettype.cpp3
-rw-r--r--indra/llcommon/llcallstack.cpp190
-rw-r--r--indra/llcommon/llcallstack.h80
-rw-r--r--indra/llcommon/llcleanup.cpp29
-rw-r--r--indra/llcommon/llcleanup.h33
-rw-r--r--indra/llcommon/llcommon.cpp5
-rw-r--r--indra/llcommon/llcoro_get_id.cpp32
-rw-r--r--indra/llcommon/llcoro_get_id.h30
-rw-r--r--indra/llcommon/llcoros.cpp147
-rw-r--r--indra/llcommon/llcoros.h26
-rw-r--r--indra/llcommon/lldependencies.cpp3
-rw-r--r--indra/llcommon/lldependencies.h12
-rw-r--r--indra/llcommon/llerror.cpp83
-rw-r--r--indra/llcommon/llerror.h91
-rw-r--r--indra/llcommon/llerrorcontrol.h5
-rw-r--r--indra/llcommon/lleventcoro.cpp3
-rw-r--r--indra/llcommon/lleventcoro.h6
-rw-r--r--indra/llcommon/llevents.cpp270
-rw-r--r--indra/llcommon/llevents.h57
-rw-r--r--indra/llcommon/llexception.cpp55
-rw-r--r--indra/llcommon/llexception.h85
-rw-r--r--indra/llcommon/llfasttimer.h49
-rw-r--r--indra/llcommon/llfile.cpp4
-rw-r--r--indra/llcommon/llfile.h4
-rw-r--r--indra/llcommon/llhandle.h81
-rw-r--r--indra/llcommon/llheteromap.cpp32
-rw-r--r--indra/llcommon/llheteromap.h95
-rw-r--r--indra/llcommon/llinitdestroyclass.cpp30
-rw-r--r--indra/llcommon/llinitdestroyclass.h175
-rw-r--r--indra/llcommon/llinitparam.cpp7
-rw-r--r--indra/llcommon/llinitparam.h1
-rw-r--r--indra/llcommon/llleap.cpp5
-rw-r--r--indra/llcommon/llleap.h6
-rw-r--r--indra/llcommon/llmake.h10
-rw-r--r--indra/llcommon/llmemory.cpp24
-rw-r--r--indra/llcommon/llmemory.h22
-rw-r--r--indra/llcommon/llmetricperformancetester.cpp2
-rw-r--r--indra/llcommon/llmetricperformancetester.h2
-rw-r--r--indra/llcommon/llpounceable.h216
-rw-r--r--indra/llcommon/llpreprocessor.h6
-rw-r--r--indra/llcommon/llprocess.cpp21
-rw-r--r--indra/llcommon/llprocess.h6
-rw-r--r--indra/llcommon/llprocessor.cpp2
-rw-r--r--indra/llcommon/llregistry.h7
-rw-r--r--indra/llcommon/llsdparam.h1
-rw-r--r--indra/llcommon/llsdserialize.cpp1
-rw-r--r--indra/llcommon/llsingleton.cpp440
-rw-r--r--indra/llcommon/llsingleton.h637
-rw-r--r--indra/llcommon/llstl.h1
-rw-r--r--indra/llcommon/llstring.cpp27
-rw-r--r--indra/llcommon/llstring.h13
-rw-r--r--indra/llcommon/llthread.cpp2
-rw-r--r--indra/llcommon/llthreadsafequeue.cpp13
-rw-r--r--indra/llcommon/llthreadsafequeue.h7
-rw-r--r--indra/llcommon/lltracerecording.cpp24
-rw-r--r--indra/llcommon/lluriparser.cpp4
-rw-r--r--indra/llcommon/lluriparser.h2
-rw-r--r--indra/llcommon/lluuid.cpp2
-rw-r--r--indra/llcommon/tests/llerror_test.cpp21
-rw-r--r--indra/llcommon/tests/llexception_test.cpp308
-rw-r--r--indra/llcommon/tests/llheteromap_test.cpp163
-rw-r--r--indra/llcommon/tests/llleap_test.cpp5
-rw-r--r--indra/llcommon/tests/llpounceable_test.cpp230
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp5
-rw-r--r--indra/llcommon/tests/llsingleton_test.cpp207
-rw-r--r--indra/llcommon/tests/wrapllerrs.h8
73 files changed, 5245 insertions, 633 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 907dbab8f8..16b42dad38 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -10,7 +10,6 @@ include(Boost)
include(LLSharedLibs)
include(JsonCpp)
include(GoogleBreakpad)
-include(GooglePerfTools)
include(Copy3rdPartyLibs)
include(ZLIB)
include(URIPARSER)
@@ -40,8 +39,11 @@ set(llcommon_SOURCE_FILES
llbase64.cpp
llbitpack.cpp
llcallbacklist.cpp
+ llcallstack.cpp
+ llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
+ llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@@ -58,6 +60,7 @@ set(llcommon_SOURCE_FILES
lleventfilter.cpp
llevents.cpp
lleventtimer.cpp
+ llexception.cpp
llfasttimer.cpp
llfile.cpp
llfindlocale.cpp
@@ -65,7 +68,9 @@ set(llcommon_SOURCE_FILES
llformat.cpp
llframetimer.cpp
llheartbeat.cpp
+ llheteromap.cpp
llinitparam.cpp
+ llinitdestroyclass.cpp
llinstancetracker.cpp
llleap.cpp
llleaplistener.cpp
@@ -114,6 +119,7 @@ set(llcommon_SOURCE_FILES
llworkerthread.cpp
timing.cpp
u64.cpp
+ StackWalker.cpp
)
set(llcommon_HEADER_FILES
@@ -134,8 +140,11 @@ set(llcommon_HEADER_FILES
llbitpack.h
llboost.h
llcallbacklist.h
+ llcallstack.h
+ llcleanup.h
llcommon.h
llcommonutils.h
+ llcoro_get_id.h
llcoros.h
llcrc.h
llcriticaldamp.h
@@ -157,6 +166,7 @@ set(llcommon_HEADER_FILES
lleventfilter.h
llevents.h
lleventemitter.h
+ llexception.h
llfasttimer.h
llfile.h
llfindlocale.h
@@ -166,7 +176,9 @@ set(llcommon_HEADER_FILES
llhandle.h
llhash.h
llheartbeat.h
+ llheteromap.h
llindexedvector.h
+ llinitdestroyclass.h
llinitparam.h
llinstancetracker.h
llkeythrottle.h
@@ -183,6 +195,7 @@ set(llcommon_HEADER_FILES
llmortician.h
llnametable.h
llpointer.h
+ llpounceable.h
llpredicate.h
llpreprocessor.h
llpriqueuemap.h
@@ -235,6 +248,7 @@ set(llcommon_HEADER_FILES
stringize.h
timer.h
u64.h
+ StackWalker.h
)
set_source_files_properties(${llcommon_HEADER_FILES}
@@ -244,13 +258,13 @@ list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES})
if(LLCOMMON_LINK_SHARED)
add_library (llcommon SHARED ${llcommon_SOURCE_FILES})
- if(NOT WORD_SIZE EQUAL 32)
+ if(NOT ADDRESS_SIZE EQUAL 32)
if(WINDOWS)
- add_definitions(/FIXED:NO)
+ ##add_definitions(/FIXED:NO)
else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
add_definitions(-fPIC)
endif(WINDOWS)
- endif(NOT WORD_SIZE EQUAL 32)
+ endif(NOT ADDRESS_SIZE EQUAL 32)
if(WINDOWS)
# always generate llcommon.pdb, even for "Release" builds
set_target_properties(llcommon PROPERTIES LINK_FLAGS "/DEBUG")
@@ -315,7 +329,7 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
@@ -327,9 +341,12 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
+
+## llexception_test.cpp isn't a regression test, and doesn't need to be run
+## every build. It's to help a developer make implementation choices about
+## throwing and catching exceptions.
+##LL_ADD_INTEGRATION_TEST(llexception "" "${test_libs}")
- # *TODO - reenable these once tcmalloc libs no longer break the build.
- #ADD_BUILD_TEST(llallocator llcommon)
- #ADD_BUILD_TEST(llallocator_heap_profile llcommon)
- #ADD_BUILD_TEST(llmemtype llcommon)
endif (LL_TESTS)
diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp
new file mode 100644
index 0000000000..c0d3104099
--- /dev/null
+++ b/indra/llcommon/StackWalker.cpp
@@ -0,0 +1,1388 @@
+/**********************************************************************
+ *
+ * StackWalker.cpp
+ * http://stackwalker.codeplex.com/
+ *
+ *
+ * $LicenseInfo:firstyear=2016&license=bsd$
+ *
+ * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/
+ *
+ * History:
+ * 2005-07-27 v1 - First public release on http://www.codeproject.com/
+ * 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
+ * (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,
+ * 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
+ * (thanks to owillebo: http://www.codeproject.com/script/profile/whos_who.asp?id=536268)
+ * 2005-08-05 v5 - Removed most Lint (http://www.gimpel.com/) errors... thanks to Okko Willeboordse!
+ * 2008-08-04 v6 - Fixed Bug: Missing LEAK-end-tag
+ * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2502890#xx2502890xx
+ * Fixed Bug: Compiled with "WIN32_LEAN_AND_MEAN"
+ * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=1824718#xx1824718xx
+ * Fixed Bug: Compiling with "/Wall"
+ * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2638243#xx2638243xx
+ * Fixed Bug: Now checking SymUseSymSrv
+ * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1388979#xx1388979xx
+ * Fixed Bug: Support for recursive function calls
+ * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1434538#xx1434538xx
+ * Fixed Bug: Missing FreeLibrary call in "GetModuleListTH32"
+ * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1326923#xx1326923xx
+ * Fixed Bug: SymDia is number 7, not 9!
+ * 2008-09-11 v7 For some (undocumented) reason, dbhelp.h is needing a packing of 8!
+ * Thanks to Teajay which reported the bug...
+ * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2718933#xx2718933xx
+ * 2008-11-27 v8 Debugging Tools for Windows are now stored in a different directory
+ * Thanks to Luiz Salamon which reported this "bug"...
+ * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2822736#xx2822736xx
+ * 2009-04-10 v9 License slihtly corrected (<ORGANIZATION> replaced)
+ * 2009-11-01 v10 Moved to http://stackwalker.codeplex.com/
+ * 2009-11-02 v11 Now try to use IMAGEHLP_MODULE64_V3 if available
+ * 2010-04-15 v12 Added support for VS2010 RTM
+ * 2010-05-25 v13 Now using secure MyStrcCpy. Thanks to luke.simon:
+ * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=3477467#xx3477467xx
+ * 2013-01-07 v14 Runtime Check Error VS2010 Debug Builds fixed:
+ * http://stackwalker.codeplex.com/workitem/10511
+ *
+ *
+ * LICENSE (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2005-2013, 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
+
+#include <windows.h>
+#include <tchar.h>
+#include <stdio.h>
+#include <stdlib.h>
+#pragma comment(lib, "version.lib") // for "VerQueryValue"
+#pragma warning(disable:4826)
+
+#include "StackWalker.h"
+
+
+// If VC7 and later, then use the shipped 'dbghelp.h'-file
+#pragma pack(push,8)
+#if _MSC_VER >= 1300
+#include <dbghelp.h>
+#else
+// inline the important dbghelp.h-declarations...
+typedef enum {
+ SymNone = 0,
+ SymCoff,
+ SymCv,
+ SymPdb,
+ SymExport,
+ SymDeferred,
+ SymSym,
+ SymDia,
+ SymVirtual,
+ NumSymTypes
+} SYM_TYPE;
+typedef struct _IMAGEHLP_LINE64 {
+ DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64)
+ PVOID Key; // internal
+ DWORD LineNumber; // line number in file
+ PCHAR FileName; // full filename
+ DWORD64 Address; // first instruction of line
+} IMAGEHLP_LINE64, *PIMAGEHLP_LINE64;
+typedef struct _IMAGEHLP_MODULE64 {
+ DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
+ DWORD64 BaseOfImage; // base load address of module
+ DWORD ImageSize; // virtual size of the loaded module
+ DWORD TimeDateStamp; // date/time stamp from pe header
+ DWORD CheckSum; // checksum from the pe header
+ DWORD NumSyms; // number of symbols in the symbol table
+ SYM_TYPE SymType; // type of symbols loaded
+ CHAR ModuleName[32]; // module name
+ CHAR ImageName[256]; // image name
+ CHAR LoadedImageName[256]; // symbol file name
+} IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64;
+typedef struct _IMAGEHLP_SYMBOL64 {
+ DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64)
+ DWORD64 Address; // virtual address including dll base address
+ DWORD Size; // estimated size of symbol, can be zero
+ DWORD Flags; // info about the symbols, see the SYMF defines
+ DWORD MaxNameLength; // maximum size of symbol name in 'Name'
+ CHAR Name[1]; // symbol name (null terminated string)
+} IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64;
+typedef enum {
+ AddrMode1616,
+ AddrMode1632,
+ AddrModeReal,
+ AddrModeFlat
+} ADDRESS_MODE;
+typedef struct _tagADDRESS64 {
+ DWORD64 Offset;
+ WORD Segment;
+ ADDRESS_MODE Mode;
+} ADDRESS64, *LPADDRESS64;
+typedef struct _KDHELP64 {
+ DWORD64 Thread;
+ DWORD ThCallbackStack;
+ DWORD ThCallbackBStore;
+ DWORD NextCallback;
+ DWORD FramePointer;
+ DWORD64 KiCallUserMode;
+ DWORD64 KeUserCallbackDispatcher;
+ DWORD64 SystemRangeStart;
+ DWORD64 Reserved[8];
+} KDHELP64, *PKDHELP64;
+typedef struct _tagSTACKFRAME64 {
+ ADDRESS64 AddrPC; // program counter
+ ADDRESS64 AddrReturn; // return address
+ ADDRESS64 AddrFrame; // frame pointer
+ ADDRESS64 AddrStack; // stack pointer
+ ADDRESS64 AddrBStore; // backing store pointer
+ PVOID FuncTableEntry; // pointer to pdata/fpo or NULL
+ DWORD64 Params[4]; // possible arguments to the function
+ BOOL Far; // WOW far call
+ BOOL Virtual; // is this a virtual frame?
+ DWORD64 Reserved[3];
+ KDHELP64 KdHelp;
+} STACKFRAME64, *LPSTACKFRAME64;
+typedef
+BOOL
+(__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)(
+ HANDLE hProcess,
+ DWORD64 qwBaseAddress,
+ PVOID lpBuffer,
+ DWORD nSize,
+ LPDWORD lpNumberOfBytesRead
+ );
+typedef
+PVOID
+(__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)(
+ HANDLE hProcess,
+ DWORD64 AddrBase
+ );
+typedef
+DWORD64
+(__stdcall *PGET_MODULE_BASE_ROUTINE64)(
+ HANDLE hProcess,
+ DWORD64 Address
+ );
+typedef
+DWORD64
+(__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)(
+ HANDLE hProcess,
+ HANDLE hThread,
+ LPADDRESS64 lpaddr
+ );
+#define SYMOPT_CASE_INSENSITIVE 0x00000001
+#define SYMOPT_UNDNAME 0x00000002
+#define SYMOPT_DEFERRED_LOADS 0x00000004
+#define SYMOPT_NO_CPP 0x00000008
+#define SYMOPT_LOAD_LINES 0x00000010
+#define SYMOPT_OMAP_FIND_NEAREST 0x00000020
+#define SYMOPT_LOAD_ANYTHING 0x00000040
+#define SYMOPT_IGNORE_CVREC 0x00000080
+#define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100
+#define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200
+#define SYMOPT_EXACT_SYMBOLS 0x00000400
+#define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800
+#define SYMOPT_IGNORE_NT_SYMPATH 0x00001000
+#define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000
+#define SYMOPT_PUBLICS_ONLY 0x00004000
+#define SYMOPT_NO_PUBLICS 0x00008000
+#define SYMOPT_AUTO_PUBLICS 0x00010000
+#define SYMOPT_NO_IMAGE_SEARCH 0x00020000
+#define SYMOPT_SECURE 0x00040000
+#define SYMOPT_DEBUG 0x80000000
+#define UNDNAME_COMPLETE (0x0000) // Enable full undecoration
+#define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration;
+#endif // _MSC_VER < 1300
+#pragma pack(pop)
+
+// Some missing defines (for VC5/6):
+#ifndef INVALID_FILE_ATTRIBUTES
+#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+#endif
+
+
+// secure-CRT_functions are only available starting with VC8
+#if _MSC_VER < 1400
+#define strcpy_s(dst, len, src) strcpy(dst, src)
+#define strncpy_s(dst, len, src, maxLen) strncpy(dst, len, src)
+#define strcat_s(dst, len, src) strcat(dst, src)
+#define _snprintf_s _snprintf
+#define _tcscat_s _tcscat
+#endif
+
+static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc)
+{
+ if (nMaxDestSize <= 0) return;
+ strncpy_s(szDest, nMaxDestSize, szSrc, _TRUNCATE);
+ szDest[nMaxDestSize-1] = 0; // INFO: _TRUNCATE will ensure that it is nul-terminated; but with older compilers (<1400) it uses "strncpy" and this does not!)
+} // MyStrCpy
+
+// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL')
+#define USED_CONTEXT_FLAGS CONTEXT_FULL
+
+
+class StackWalkerInternal
+{
+public:
+ StackWalkerInternal(StackWalker *parent, HANDLE hProcess)
+ {
+ m_parent = parent;
+ m_hDbhHelp = NULL;
+ pSC = NULL;
+ m_hProcess = hProcess;
+ m_szSymPath = NULL;
+ pSFTA = NULL;
+ pSGLFA = NULL;
+ pSGMB = NULL;
+ pSGMI = NULL;
+ pSGO = NULL;
+ pSGSFA = NULL;
+ pSI = NULL;
+ pSLM = NULL;
+ pSSO = NULL;
+ pSW = NULL;
+ pUDSN = NULL;
+ pSGSP = NULL;
+ }
+ ~StackWalkerInternal()
+ {
+ if (pSC != NULL)
+ pSC(m_hProcess); // SymCleanup
+ if (m_hDbhHelp != NULL)
+ FreeLibrary(m_hDbhHelp);
+ m_hDbhHelp = NULL;
+ m_parent = NULL;
+ if(m_szSymPath != NULL)
+ free(m_szSymPath);
+ m_szSymPath = NULL;
+ }
+ BOOL Init(LPCSTR szSymPath)
+ {
+ if (m_parent == NULL)
+ return FALSE;
+ // Dynamically load the Entry-Points for dbghelp.dll:
+ // First try to load the newsest one from
+ TCHAR szTemp[4096];
+ // But before wqe do this, we first check if the ".local" file exists
+ if (GetModuleFileName(NULL, szTemp, 4096) > 0)
+ {
+ _tcscat_s(szTemp, _T(".local"));
+ if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES)
+ {
+ // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows"
+ // Ok, first try the new path according to the archtitecture:
+#ifdef _M_IX86
+ if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) )
+ {
+ _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll"));
+ // now check if the file exists:
+ if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES)
+ {
+ m_hDbhHelp = LoadLibrary(szTemp);
+ }
+ }
+#elif _M_X64
+ if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) )
+ {
+ _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll"));
+ // now check if the file exists:
+ if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES)
+ {
+ m_hDbhHelp = LoadLibrary(szTemp);
+ }
+ }
+#elif _M_IA64
+ if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) )
+ {
+ _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll"));
+ // now check if the file exists:
+ if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES)
+ {
+ m_hDbhHelp = LoadLibrary(szTemp);
+ }
+ }
+#endif
+ // If still not found, try the old directories...
+ if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) )
+ {
+ _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll"));
+ // now check if the file exists:
+ if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES)
+ {
+ m_hDbhHelp = LoadLibrary(szTemp);
+ }
+ }
+#if defined _M_X64 || defined _M_IA64
+ // Still not found? Then try to load the (old) 64-Bit version:
+ if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) )
+ {
+ _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll"));
+ if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES)
+ {
+ m_hDbhHelp = LoadLibrary(szTemp);
+ }
+ }
+#endif
+ }
+ }
+ 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;
+ pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" );
+ pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" );
+
+ pSW = (tSW) GetProcAddress(m_hDbhHelp, "StackWalk64" );
+ pSGO = (tSGO) GetProcAddress(m_hDbhHelp, "SymGetOptions" );
+ pSSO = (tSSO) GetProcAddress(m_hDbhHelp, "SymSetOptions" );
+
+ pSFTA = (tSFTA) GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64" );
+ pSGLFA = (tSGLFA) GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64" );
+ pSGMB = (tSGMB) GetProcAddress(m_hDbhHelp, "SymGetModuleBase64" );
+ pSGMI = (tSGMI) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" );
+ pSGSFA = (tSGSFA) GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64" );
+ pUDSN = (tUDSN) GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName" );
+ pSLM = (tSLM) GetProcAddress(m_hDbhHelp, "SymLoadModule64" );
+ pSGSP =(tSGSP) GetProcAddress(m_hDbhHelp, "SymGetSearchPath" );
+
+ if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
+ pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
+ pSW == NULL || pUDSN == NULL || pSLM == NULL )
+ {
+ FreeLibrary(m_hDbhHelp);
+ m_hDbhHelp = NULL;
+ pSC = NULL;
+ return FALSE;
+ }
+
+ // SymInitialize
+ if (szSymPath != NULL)
+ 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;
+ //symOptions |= SYMOPT_NO_PROMPTS;
+ // SymSetOptions
+ symOptions = this->pSSO(symOptions);
+
+ char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0};
+ if (this->pSGSP != NULL)
+ {
+ if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE)
+ this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0);
+ }
+ char szUserName[1024] = {0};
+ DWORD dwSize = 1024;
+ GetUserNameA(szUserName, &dwSize);
+ this->m_parent->OnSymInit(buf, symOptions, szUserName);
+
+ return TRUE;
+ }
+
+ StackWalker *m_parent;
+
+ HMODULE m_hDbhHelp;
+ HANDLE m_hProcess;
+ LPSTR m_szSymPath;
+
+#pragma pack(push,8)
+typedef struct IMAGEHLP_MODULE64_V3 {
+ DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
+ DWORD64 BaseOfImage; // base load address of module
+ DWORD ImageSize; // virtual size of the loaded module
+ DWORD TimeDateStamp; // date/time stamp from pe header
+ DWORD CheckSum; // checksum from the pe header
+ DWORD NumSyms; // number of symbols in the symbol table
+ SYM_TYPE SymType; // type of symbols loaded
+ CHAR ModuleName[32]; // module name
+ CHAR ImageName[256]; // image name
+ CHAR LoadedImageName[256]; // symbol file name
+ // new elements: 07-Jun-2002
+ CHAR LoadedPdbName[256]; // pdb file name
+ DWORD CVSig; // Signature of the CV record in the debug directories
+ CHAR CVData[MAX_PATH * 3]; // Contents of the CV record
+ DWORD PdbSig; // Signature of PDB
+ GUID PdbSig70; // Signature of PDB (VC 7 and up)
+ DWORD PdbAge; // DBI age of pdb
+ BOOL PdbUnmatched; // loaded an unmatched pdb
+ BOOL DbgUnmatched; // loaded an unmatched dbg
+ BOOL LineNumbers; // we have line number information
+ BOOL GlobalSymbols; // we have internal symbol information
+ BOOL TypeInfo; // we have type information
+ // new elements: 17-Dec-2003
+ BOOL SourceIndexed; // pdb supports source server
+ BOOL Publics; // contains public symbols
+};
+
+typedef struct IMAGEHLP_MODULE64_V2 {
+ DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
+ DWORD64 BaseOfImage; // base load address of module
+ DWORD ImageSize; // virtual size of the loaded module
+ DWORD TimeDateStamp; // date/time stamp from pe header
+ DWORD CheckSum; // checksum from the pe header
+ DWORD NumSyms; // number of symbols in the symbol table
+ SYM_TYPE SymType; // type of symbols loaded
+ CHAR ModuleName[32]; // module name
+ CHAR ImageName[256]; // image name
+ CHAR LoadedImageName[256]; // symbol file name
+};
+#pragma pack(pop)
+
+
+ // SymCleanup()
+ typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
+ tSC pSC;
+
+ // SymFunctionTableAccess64()
+ typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase );
+ tSFTA pSFTA;
+
+ // SymGetLineFromAddr64()
+ typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
+ OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line );
+ tSGLFA pSGLFA;
+
+ // SymGetModuleBase64()
+ typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr );
+ tSGMB pSGMB;
+
+ // SymGetModuleInfo64()
+ typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V3 *ModuleInfo );
+ tSGMI pSGMI;
+
+ // SymGetOptions()
+ typedef DWORD (__stdcall *tSGO)( VOID );
+ tSGO pSGO;
+
+ // SymGetSymFromAddr64()
+ typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
+ OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol );
+ tSGSFA pSGSFA;
+
+ // SymInitialize()
+ typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
+ tSI pSI;
+
+ // SymLoadModule64()
+ typedef DWORD64 (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile,
+ IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll );
+ tSLM pSLM;
+
+ // SymSetOptions()
+ typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
+ tSSO pSSO;
+
+ // StackWalk64()
+ typedef BOOL (__stdcall *tSW)(
+ DWORD MachineType,
+ HANDLE hProcess,
+ HANDLE hThread,
+ LPSTACKFRAME64 StackFrame,
+ PVOID ContextRecord,
+ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
+ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
+ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
+ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );
+ tSW pSW;
+
+ // UnDecorateSymbolName()
+ typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
+ DWORD UndecoratedLength, DWORD Flags );
+ tUDSN pUDSN;
+
+ typedef BOOL (__stdcall WINAPI *tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength);
+ tSGSP pSGSP;
+
+
+private:
+ // **************************************** ToolHelp32 ************************
+ #define MAX_MODULE_NAME32 255
+ #define TH32CS_SNAPMODULE 0x00000008
+ #pragma pack( push, 8 )
+ typedef struct tagMODULEENTRY32
+ {
+ DWORD dwSize;
+ DWORD th32ModuleID; // This module
+ DWORD th32ProcessID; // owning process
+ DWORD GlblcntUsage; // Global usage count on the module
+ DWORD ProccntUsage; // Module usage count in th32ProcessID's context
+ BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
+ DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
+ HMODULE hModule; // The hModule of this module in th32ProcessID's context
+ char szModule[MAX_MODULE_NAME32 + 1];
+ char szExePath[MAX_PATH];
+ } MODULEENTRY32;
+ typedef MODULEENTRY32 * PMODULEENTRY32;
+ typedef MODULEENTRY32 * LPMODULEENTRY32;
+ #pragma pack( pop )
+
+ BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid)
+ {
+ // CreateToolhelp32Snapshot()
+ typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID);
+ // Module32First()
+ typedef BOOL (__stdcall *tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
+ // Module32Next()
+ typedef BOOL (__stdcall *tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
+
+ // try both dlls...
+ const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") };
+ HINSTANCE hToolhelp = NULL;
+ tCT32S pCT32S = NULL;
+ tM32F pM32F = NULL;
+ tM32N pM32N = NULL;
+
+ HANDLE hSnap;
+ MODULEENTRY32 me;
+ me.dwSize = sizeof(me);
+ BOOL keepGoing;
+ size_t i;
+
+ for (i = 0; i<(sizeof(dllname) / sizeof(dllname[0])); i++ )
+ {
+ hToolhelp = LoadLibrary( dllname[i] );
+ if (hToolhelp == NULL)
+ continue;
+ pCT32S = (tCT32S) GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot");
+ pM32F = (tM32F) GetProcAddress(hToolhelp, "Module32First");
+ pM32N = (tM32N) GetProcAddress(hToolhelp, "Module32Next");
+ if ( (pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL) )
+ break; // found the functions!
+ FreeLibrary(hToolhelp);
+ hToolhelp = NULL;
+ }
+
+ if (hToolhelp == NULL)
+ return FALSE;
+
+ hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
+ if (hSnap == (HANDLE) -1)
+ {
+ FreeLibrary(hToolhelp);
+ return FALSE;
+ }
+
+ keepGoing = !!pM32F( hSnap, &me );
+ int cnt = 0;
+ while (keepGoing)
+ {
+ this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize);
+ cnt++;
+ keepGoing = !!pM32N( hSnap, &me );
+ }
+ CloseHandle(hSnap);
+ FreeLibrary(hToolhelp);
+ if (cnt <= 0)
+ return FALSE;
+ return TRUE;
+ } // GetModuleListTH32
+
+ // **************************************** PSAPI ************************
+ typedef struct _MODULEINFO {
+ LPVOID lpBaseOfDll;
+ DWORD SizeOfImage;
+ LPVOID EntryPoint;
+ } MODULEINFO, *LPMODULEINFO;
+
+ BOOL GetModuleListPSAPI(HANDLE hProcess)
+ {
+ // EnumProcessModules()
+ typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded );
+ // GetModuleFileNameEx()
+ typedef DWORD (__stdcall *tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
+ // GetModuleBaseName()
+ typedef DWORD (__stdcall *tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
+ // GetModuleInformation()
+ typedef BOOL (__stdcall *tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize );
+
+ HINSTANCE hPsapi;
+ tEPM pEPM;
+ tGMFNE pGMFNE;
+ tGMBN pGMBN;
+ tGMI pGMI;
+
+ DWORD i;
+ //ModuleEntry e;
+ DWORD cbNeeded;
+ MODULEINFO mi;
+ HMODULE *hMods = 0;
+ char *tt = NULL;
+ char *tt2 = NULL;
+ const SIZE_T TTBUFLEN = 8096;
+ int cnt = 0;
+
+ hPsapi = LoadLibrary( _T("psapi.dll") );
+ if (hPsapi == NULL)
+ return FALSE;
+
+ pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" );
+ pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" );
+ pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" );
+ pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
+ if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) )
+ {
+ // we couldn´t find all functions
+ FreeLibrary(hPsapi);
+ return FALSE;
+ }
+
+ hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE)));
+ tt = (char*) malloc(sizeof(char) * TTBUFLEN);
+ tt2 = (char*) malloc(sizeof(char) * TTBUFLEN);
+ if ( (hMods == NULL) || (tt == NULL) || (tt2 == NULL) )
+ goto cleanup;
+
+ if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) )
+ {
+ //_ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle );
+ goto cleanup;
+ }
+
+ if ( cbNeeded > TTBUFLEN )
+ {
+ //_ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) );
+ goto cleanup;
+ }
+
+ for ( i = 0; i < cbNeeded / sizeof(hMods[0]); i++ )
+ {
+ // base address, size
+ pGMI(hProcess, hMods[i], &mi, sizeof(mi));
+ // image file name
+ tt[0] = 0;
+ pGMFNE(hProcess, hMods[i], tt, TTBUFLEN );
+ // module name
+ tt2[0] = 0;
+ pGMBN(hProcess, hMods[i], tt2, TTBUFLEN );
+
+ DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage);
+ if (dwRes != ERROR_SUCCESS)
+ this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0);
+ cnt++;
+ }
+
+ cleanup:
+ if (hPsapi != NULL) FreeLibrary(hPsapi);
+ if (tt2 != NULL) free(tt2);
+ if (tt != NULL) free(tt);
+ if (hMods != NULL) free(hMods);
+
+ return cnt != 0;
+ } // GetModuleListPSAPI
+
+ DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size)
+ {
+ CHAR *szImg = _strdup(img);
+ CHAR *szMod = _strdup(mod);
+ DWORD result = ERROR_SUCCESS;
+ if ( (szImg == NULL) || (szMod == NULL) )
+ result = ERROR_NOT_ENOUGH_MEMORY;
+ else
+ {
+ if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0)
+ result = GetLastError();
+ }
+ ULONGLONG fileVersion = 0;
+ if ( (m_parent != NULL) && (szImg != NULL) )
+ {
+ // try to retrive the file-version:
+ if ( (this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0)
+ {
+ VS_FIXEDFILEINFO *fInfo = NULL;
+ DWORD dwHandle;
+ DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle);
+ if (dwSize > 0)
+ {
+ LPVOID vData = malloc(dwSize);
+ if (vData != NULL)
+ {
+ if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0)
+ {
+ UINT len;
+ TCHAR szSubBlock[] = _T("\\");
+ if (VerQueryValue(vData, szSubBlock, (LPVOID*) &fInfo, &len) == 0)
+ fInfo = NULL;
+ else
+ {
+ fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32);
+ }
+ }
+ free(vData);
+ }
+ }
+ }
+
+ // Retrive some additional-infos about the module
+ IMAGEHLP_MODULE64_V3 Module;
+ const char *szSymType = "-unknown-";
+ if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE)
+ {
+ switch(Module.SymType)
+ {
+ case SymNone:
+ szSymType = "-nosymbols-";
+ break;
+ case SymCoff: // 1
+ szSymType = "COFF";
+ break;
+ case SymCv: // 2
+ szSymType = "CV";
+ break;
+ case SymPdb: // 3
+ szSymType = "PDB";
+ break;
+ case SymExport: // 4
+ szSymType = "-exported-";
+ break;
+ case SymDeferred: // 5
+ szSymType = "-deferred-";
+ break;
+ case SymSym: // 6
+ szSymType = "SYM";
+ break;
+ case 7: // SymDia:
+ szSymType = "DIA";
+ break;
+ case 8: //SymVirtual:
+ szSymType = "Virtual";
+ break;
+ }
+ }
+ LPCSTR pdbName = Module.LoadedImageName;
+ if (Module.LoadedPdbName[0] != 0)
+ pdbName = Module.LoadedPdbName;
+ this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, pdbName, fileVersion);
+ }
+ if (szImg != NULL) free(szImg);
+ if (szMod != NULL) free(szMod);
+ return result;
+ }
+public:
+ BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId)
+ {
+ // first try toolhelp32
+ if (GetModuleListTH32(hProcess, dwProcessId))
+ return true;
+ // then try psapi
+ return GetModuleListPSAPI(hProcess);
+ }
+
+
+ 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;
+ }
+ // First try to use the larger ModuleInfo-Structure
+ pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3);
+ void *pData = malloc(4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites...
+ if (pData == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+ memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3));
+ static bool s_useV3Version = true;
+ if (s_useV3Version)
+ {
+ if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE)
+ {
+ // only copy as much memory as is reserved...
+ memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3));
+ pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3);
+ free(pData);
+ return TRUE;
+ }
+ s_useV3Version = false; // to prevent unneccessarry calls with the larger struct...
+ }
+
+ // could not retrive the bigger structure, try with the smaller one (as defined in VC7.1)...
+ pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);
+ memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2));
+ if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE)
+ {
+ // only copy as much memory as is reserved...
+ memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2));
+ pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);
+ free(pData);
+ return TRUE;
+ }
+ free(pData);
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+};
+
+// #############################################################
+StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess)
+{
+ this->m_verbose = true;
+ this->m_options = OptionsAll;
+ this->m_modulesLoaded = FALSE;
+ this->m_hProcess = hProcess;
+ this->m_sw = new StackWalkerInternal(this, this->m_hProcess);
+ this->m_dwProcessId = dwProcessId;
+ this->m_szSymPath = NULL;
+ this->m_MaxRecursionCount = 1000;
+}
+StackWalker::StackWalker(bool verbose, int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess)
+{
+ this->m_verbose = verbose;
+ this->m_options = options;
+ this->m_modulesLoaded = FALSE;
+ this->m_hProcess = hProcess;
+ this->m_sw = new StackWalkerInternal(this, this->m_hProcess);
+ this->m_dwProcessId = dwProcessId;
+ if (szSymPath != NULL)
+ {
+ this->m_szSymPath = _strdup(szSymPath);
+ this->m_options |= SymBuildPath;
+ }
+ else
+ this->m_szSymPath = NULL;
+ this->m_MaxRecursionCount = 1000;
+}
+
+StackWalker::~StackWalker()
+{
+ if (m_szSymPath != NULL)
+ free(m_szSymPath);
+ m_szSymPath = NULL;
+ if (this->m_sw != NULL)
+ delete this->m_sw;
+ this->m_sw = NULL;
+}
+
+BOOL StackWalker::LoadModules()
+{
+ if (this->m_sw == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+ if (m_modulesLoaded != FALSE)
+ return TRUE;
+
+ // Build the sym-path:
+ char *szSymPath = NULL;
+ if ( (this->m_options & SymBuildPath) != 0)
+ {
+ const size_t nSymPathLen = 4096;
+ szSymPath = (char*) malloc(nSymPathLen);
+ if (szSymPath == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return FALSE;
+ }
+ szSymPath[0] = 0;
+ // Now first add the (optional) provided sympath:
+ if (this->m_szSymPath != NULL)
+ {
+ strcat_s(szSymPath, nSymPathLen, this->m_szSymPath);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+
+ strcat_s(szSymPath, nSymPathLen, ".;");
+
+ const size_t nTempLen = 1024;
+ char szTemp[nTempLen];
+ // Now add the current directory:
+ if (GetCurrentDirectoryA(nTempLen, szTemp) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+
+ // Now add the path for the main-module:
+ if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ for (char *p = (szTemp+strlen(szTemp)-1); p >= szTemp; --p)
+ {
+ // locate the rightmost path separator
+ if ( (*p == '\\') || (*p == '/') || (*p == ':') )
+ {
+ *p = 0;
+ break;
+ }
+ } // for (search for path separator...)
+ if (strlen(szTemp) > 0)
+ {
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+ }
+ if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+ if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+ if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ // also add the "system32"-directory:
+ strcat_s(szTemp, nTempLen, "\\system32");
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, ";");
+ }
+
+ if ( (this->m_options & SymUseSymSrv) != 0)
+ {
+ if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0)
+ {
+ szTemp[nTempLen-1] = 0;
+ strcat_s(szSymPath, nSymPathLen, "SRV*");
+ strcat_s(szSymPath, nSymPathLen, szTemp);
+ strcat_s(szSymPath, nSymPathLen, "\\websymbols");
+ strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;");
+ }
+ else
+ strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;");
+ }
+ } // if SymBuildPath
+
+ // First Init the whole stuff...
+ BOOL bRet = this->m_sw->Init(szSymPath);
+ if (szSymPath != NULL) free(szSymPath); szSymPath = NULL;
+ if (bRet == FALSE)
+ {
+ this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0);
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+
+ bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId);
+ if (bRet != FALSE)
+ m_modulesLoaded = TRUE;
+ return bRet;
+}
+
+
+// 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
+// of dbghelp.dll) it is "safe" to use a static-variable
+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)
+{
+ m_verbose = verbose;
+ CONTEXT c;
+ CallstackEntry csEntry;
+ IMAGEHLP_SYMBOL64 *pSym = NULL;
+ StackWalkerInternal::IMAGEHLP_MODULE64_V3 Module;
+ IMAGEHLP_LINE64 Line;
+ int frameNum;
+ bool bLastEntryCalled = true;
+ int curRecursionCount = 0;
+
+ if (m_modulesLoaded == FALSE)
+ this->LoadModules(); // ignore the result...
+
+ if (this->m_sw->m_hDbhHelp == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+
+ s_readMemoryFunction = readMemoryFunction;
+ s_readMemoryFunction_UserData = pUserData;
+
+ if (context == NULL)
+ {
+ // If no context is provided, capture the context
+ // See: https://stackwalker.codeplex.com/discussions/446958
+#if _WIN32_WINNT <= 0x0501
+ // If we need to support XP, we need to use the "old way", because "GetThreadId" is not available!
+ if (hThread == GetCurrentThread())
+#else
+ if (GetThreadId(hThread) == GetCurrentThreadId())
+#endif
+ {
+ GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, USED_CONTEXT_FLAGS);
+ }
+ else
+ {
+ SuspendThread(hThread);
+ memset(&c, 0, sizeof(CONTEXT));
+ c.ContextFlags = USED_CONTEXT_FLAGS;
+ if (GetThreadContext(hThread, &c) == FALSE)
+ {
+ ResumeThread(hThread);
+ return FALSE;
+ }
+ }
+ }
+ else
+ c = *context;
+
+ // init STACKFRAME for first call
+ STACKFRAME64 s; // in/out stackframe
+ memset(&s, 0, sizeof(s));
+ DWORD imageType;
+#ifdef _M_IX86
+ // normally, call ImageNtHeader() and use machine info from PE header
+ imageType = IMAGE_FILE_MACHINE_I386;
+ s.AddrPC.Offset = c.Eip;
+ s.AddrPC.Mode = AddrModeFlat;
+ s.AddrFrame.Offset = c.Ebp;
+ s.AddrFrame.Mode = AddrModeFlat;
+ s.AddrStack.Offset = c.Esp;
+ s.AddrStack.Mode = AddrModeFlat;
+#elif _M_X64
+ imageType = IMAGE_FILE_MACHINE_AMD64;
+ s.AddrPC.Offset = c.Rip;
+ s.AddrPC.Mode = AddrModeFlat;
+ s.AddrFrame.Offset = c.Rsp;
+ s.AddrFrame.Mode = AddrModeFlat;
+ s.AddrStack.Offset = c.Rsp;
+ s.AddrStack.Mode = AddrModeFlat;
+#elif _M_IA64
+ imageType = IMAGE_FILE_MACHINE_IA64;
+ s.AddrPC.Offset = c.StIIP;
+ s.AddrPC.Mode = AddrModeFlat;
+ s.AddrFrame.Offset = c.IntSp;
+ s.AddrFrame.Mode = AddrModeFlat;
+ s.AddrBStore.Offset = c.RsBSP;
+ s.AddrBStore.Mode = AddrModeFlat;
+ s.AddrStack.Offset = c.IntSp;
+ s.AddrStack.Mode = AddrModeFlat;
+#else
+#error "Platform not supported!"
+#endif
+
+ pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
+ if (!pSym) goto cleanup; // not enough memory...
+ memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
+ pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+ pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;
+
+ memset(&Line, 0, sizeof(Line));
+ Line.SizeOfStruct = sizeof(Line);
+
+ memset(&Module, 0, sizeof(Module));
+ Module.SizeOfStruct = sizeof(Module);
+
+ for (frameNum = 0; ; ++frameNum )
+ {
+ // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())
+ // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
+ // assume that either you are done, or that the stack is so hosed that the next
+ // deeper frame could not be found.
+ // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!
+ if ( ! this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, this->m_sw->pSFTA, this->m_sw->pSGMB, NULL) )
+ {
+ // INFO: "StackWalk64" does not set "GetLastError"...
+ this->OnDbgHelpErr("StackWalk64", 0, s.AddrPC.Offset);
+ break;
+ }
+
+ csEntry.offset = s.AddrPC.Offset;
+ csEntry.name[0] = 0;
+ csEntry.undName[0] = 0;
+ csEntry.undFullName[0] = 0;
+ csEntry.offsetFromSmybol = 0;
+ csEntry.offsetFromLine = 0;
+ csEntry.lineFileName[0] = 0;
+ csEntry.lineNumber = 0;
+ csEntry.loadedImageName[0] = 0;
+ csEntry.moduleName[0] = 0;
+ if (s.AddrPC.Offset == s.AddrReturn.Offset)
+ {
+ if ( (this->m_MaxRecursionCount > 0) && (curRecursionCount > m_MaxRecursionCount) )
+ {
+ this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset);
+ break;
+ }
+ curRecursionCount++;
+ }
+ else
+ curRecursionCount = 0;
+ if (s.AddrPC.Offset != 0)
+ {
+ // we seem to have a valid PC
+ // show procedure info (SymGetSymFromAddr64())
+ if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE)
+ {
+ MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, pSym->Name);
+ // UnDecorateSymbolName()
+ this->m_sw->pUDSN( pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY );
+ this->m_sw->pUDSN( pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE );
+ }
+ else
+ {
+ this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset);
+ }
+
+ // show line number info, NT5.0-method (SymGetLineFromAddr64())
+ if (this->m_sw->pSGLFA != NULL )
+ { // yes, we have SymGetLineFromAddr64()
+ if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE)
+ {
+ csEntry.lineNumber = Line.LineNumber;
+ MyStrCpy(csEntry.lineFileName, STACKWALK_MAX_NAMELEN, Line.FileName);
+ }
+ else
+ {
+ this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset);
+ }
+ } // yes, we have SymGetLineFromAddr64()
+
+ if (m_verbose) // getting module info is very slow, skip unless we're being verbose.
+ {
+ // show module info (SymGetModuleInfo64())
+ if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module ) != FALSE)
+ { // got module info OK
+ switch ( Module.SymType )
+ {
+ case SymNone:
+ csEntry.symTypeString = "-nosymbols-";
+ break;
+ case SymCoff:
+ csEntry.symTypeString = "COFF";
+ break;
+ case SymCv:
+ csEntry.symTypeString = "CV";
+ break;
+ case SymPdb:
+ csEntry.symTypeString = "PDB";
+ break;
+ case SymExport:
+ csEntry.symTypeString = "-exported-";
+ break;
+ case SymDeferred:
+ csEntry.symTypeString = "-deferred-";
+ break;
+ case SymSym:
+ csEntry.symTypeString = "SYM";
+ break;
+#if API_VERSION_NUMBER >= 9
+ case SymDia:
+ csEntry.symTypeString = "DIA";
+ break;
+#endif
+ case 8: //SymVirtual:
+ csEntry.symTypeString = "Virtual";
+ break;
+ default:
+ //_snprintf( ty, sizeof(ty), "symtype=%ld", (long) Module.SymType );
+ csEntry.symTypeString = NULL;
+ break;
+ }
+
+ MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName);
+ csEntry.baseOfImage = Module.BaseOfImage;
+ MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName);
+ } // got module info OK
+ else
+ {
+ this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset);
+ }
+ }
+ else
+ {
+ MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, "MODULE?");
+ }
+ } // we seem to have a valid PC
+
+ CallstackEntryType et = nextEntry;
+ if (frameNum == 0)
+ et = firstEntry;
+ bLastEntryCalled = false;
+ this->OnCallstackEntry(et, csEntry);
+
+ if (s.AddrReturn.Offset == 0)
+ {
+ bLastEntryCalled = true;
+ this->OnCallstackEntry(lastEntry, csEntry);
+ SetLastError(ERROR_SUCCESS);
+ break;
+ }
+ } // for ( frameNum )
+
+ cleanup:
+ if (pSym) free( pSym );
+
+ if (bLastEntryCalled == false)
+ this->OnCallstackEntry(lastEntry, csEntry);
+
+ if (context == NULL)
+ ResumeThread(hThread);
+
+ return TRUE;
+}
+
+BOOL __stdcall StackWalker::myReadProcMem(
+ HANDLE hProcess,
+ DWORD64 qwBaseAddress,
+ PVOID lpBuffer,
+ DWORD nSize,
+ LPDWORD lpNumberOfBytesRead
+ )
+{
+ if (s_readMemoryFunction == NULL)
+ {
+ SIZE_T st;
+ BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st);
+ *lpNumberOfBytesRead = (DWORD) st;
+ //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet);
+ return bRet;
+ }
+ else
+ {
+ return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData);
+ }
+}
+
+void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
+{
+ CHAR buffer[STACKWALK_MAX_NAMELEN];
+ if (fileVersion == 0)
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName);
+ else
+ {
+ DWORD v4 = (DWORD) (fileVersion & 0xFFFF);
+ DWORD v3 = (DWORD) ((fileVersion>>16) & 0xFFFF);
+ DWORD v2 = (DWORD) ((fileVersion>>32) & 0xFFFF);
+ DWORD v1 = (DWORD) ((fileVersion>>48) & 0xFFFF);
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4);
+ }
+ if (m_verbose)
+ {
+ OnOutput(buffer);
+ }
+}
+
+void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry)
+{
+ CHAR buffer[STACKWALK_MAX_NAMELEN];
+ if ( (eType != lastEntry) && (entry.offset != 0) )
+ {
+ if (entry.name[0] == 0)
+ MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, "(function-name not available)");
+ if (entry.undName[0] != 0)
+ MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName);
+ if (entry.undFullName[0] != 0)
+ MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName);
+ if (entry.lineFileName[0] == 0)
+ {
+ MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, "(filename not available)");
+ if (entry.moduleName[0] == 0)
+ MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, "(module-name not available)");
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%p (%s): %s: %s\n", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name);
+ }
+ else
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s (%d): %s\n", entry.lineFileName, entry.lineNumber, entry.name);
+ buffer[STACKWALK_MAX_NAMELEN-1] = 0;
+ OnOutput(buffer);
+ }
+}
+
+void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr)
+{
+ CHAR buffer[STACKWALK_MAX_NAMELEN];
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr);
+ if (m_verbose)
+ {
+ OnOutput(buffer);
+ }
+}
+
+#pragma warning (disable : 4996)
+
+void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName)
+{
+ CHAR buffer[STACKWALK_MAX_NAMELEN];
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName);
+ if (m_verbose)
+ {
+ OnOutput(buffer);
+ }
+ // Also display the OS-version
+#if _MSC_VER <= 1200
+ OSVERSIONINFOA ver;
+ ZeroMemory(&ver, sizeof(OSVERSIONINFOA));
+ ver.dwOSVersionInfoSize = sizeof(ver);
+ if (GetVersionExA(&ver) != FALSE)
+ {
+ _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n",
+ ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber,
+ ver.szCSDVersion);
+ if (m_verbose)
+ {
+ OnOutput(buffer);
+ }
+ }
+#else
+ OSVERSIONINFOEXA ver;
+ ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA));
+ 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",
+ ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber,
+ ver.szCSDVersion, ver.wSuiteMask, ver.wProductType);
+ if (m_verbose)
+ {
+ OnOutput(buffer);
+ }
+ }
+#endif
+}
+
+void StackWalker::OnOutput(LPCSTR buffer)
+{
+ OutputDebugStringA(buffer);
+}
+
+#endif // LL_WINDOWS
diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h
new file mode 100644
index 0000000000..834f89c471
--- /dev/null
+++ b/indra/llcommon/StackWalker.h
@@ -0,0 +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 <windows.h>
+
+// 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;
+
+ typedef 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/llallocator.cpp b/indra/llcommon/llallocator.cpp
index 34fc28d8cc..ac97fb71dd 100644
--- a/indra/llcommon/llallocator.cpp
+++ b/indra/llcommon/llallocator.cpp
@@ -27,47 +27,6 @@
#include "linden_common.h"
#include "llallocator.h"
-#if (LL_USE_TCMALLOC && LL_USE_HEAP_PROFILER)
-
-#include "google/heap-profiler.h"
-#include "google/commandlineflags_public.h"
-
-DECLARE_bool(heap_profile_use_stack_trace);
-//DECLARE_double(tcmalloc_release_rate);
-
-void LLAllocator::setProfilingEnabled(bool should_enable)
-{
- // NULL disables dumping to disk
- static char const * const PREFIX = NULL;
- if(should_enable)
- {
- HeapProfilerSetUseStackTrace(false);
- HeapProfilerStart(PREFIX);
- }
- else
- {
- HeapProfilerStop();
- }
-}
-
-// static
-bool LLAllocator::isProfiling()
-{
- return IsHeapProfilerRunning();
-}
-
-std::string LLAllocator::getRawProfile()
-{
- // *TODO - fix google-perftools to accept an buffer to avoid this
- // malloc-copy-free cycle.
- char * buffer = GetHeapProfile();
- std::string ret = buffer;
- free(buffer);
- return ret;
-}
-
-#else // LL_USE_TCMALLOC
-
//
// stub implementations for when tcmalloc is disabled
//
@@ -87,8 +46,6 @@ std::string LLAllocator::getRawProfile()
return std::string();
}
-#endif // LL_USE_TCMALLOC
-
LLAllocatorHeapProfile const & LLAllocator::getProfile()
{
mProf.mLines.clear();
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index eb0699ad41..2c76f29020 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -48,6 +48,7 @@
#include "lleventtimer.h"
#include "google_breakpad/exception_handler.h"
#include "stringize.h"
+#include "llcleanup.h"
//
// Signal handling
@@ -177,7 +178,7 @@ LLApp::~LLApp()
if(mExceptionHandler != 0) delete mExceptionHandler;
- LLCommon::cleanupClass();
+ SUBSYSTEM_CLEANUP(LLCommon);
}
// static
diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h
index d9933b3d36..ff9a92b45f 100644
--- a/indra/llcommon/llapp.h
+++ b/indra/llcommon/llapp.h
@@ -172,12 +172,12 @@ public:
virtual bool cleanup() = 0; // Override to do application cleanup
//
- // mainLoop()
+ // frame()
//
- // Runs the application main loop. It's assumed that when you exit
- // this method, the application is in one of the cleanup states, either QUITTING or ERROR
+ // Pass control to the application for a single frame. Returns 'done'
+ // flag: if frame() returns false, it expects to be called again.
//
- virtual bool mainLoop() = 0; // Override for the application main loop. Needs to at least gracefully notice the QUITTING state and exit.
+ virtual bool frame() = 0; // Override for application body logic
//
// Crash logging
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index a548c96002..86f407cdb0 100644
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -294,9 +294,11 @@ void LLScopedLock::unlock()
bool ll_apr_warn_status(apr_status_t status)
{
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: " << buf << LL_ENDL;
+#endif
return true;
}
diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp
index 5ae2df3994..4304db36be 100644
--- a/indra/llcommon/llassettype.cpp
+++ b/indra/llcommon/llassettype.cpp
@@ -63,8 +63,7 @@ struct AssetEntry : public LLDictionaryEntry
class LLAssetDictionary : public LLSingleton<LLAssetDictionary>,
public LLDictionary<LLAssetType::EType, AssetEntry>
{
-public:
- LLAssetDictionary();
+ LLSINGLETON(LLAssetDictionary);
};
LLAssetDictionary::LLAssetDictionary()
diff --git a/indra/llcommon/llcallstack.cpp b/indra/llcommon/llcallstack.cpp
new file mode 100644
index 0000000000..8db291eed1
--- /dev/null
+++ b/indra/llcommon/llcallstack.cpp
@@ -0,0 +1,190 @@
+/**
+ * @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<std::string>& 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.size(); ++i)
+ {
+ stack.push_back(m_stack[i]);
+ }
+ }
+protected:
+ virtual void OnOutput(LPCSTR szText)
+ {
+ m_stack.push_back(szText);
+ }
+ std::vector<std::string> m_stack;
+};
+#else
+// Stub - not implemented currently on other platforms.
+class LLCallStackImpl
+{
+public:
+ LLCallStackImpl() {}
+ ~LLCallStackImpl() {}
+ void getStack(std::vector<std::string>& 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 (std::vector<std::string>::const_iterator it = m_strings.begin();
+ it != m_strings.end(); ++it)
+ {
+ if (it->find(str) != std::string::npos)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::ostream& operator<<(std::ostream& s, const LLCallStack& call_stack)
+{
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ std::vector<std::string>::const_iterator it;
+ for (it=call_stack.m_strings.begin(); it!=call_stack.m_strings.end(); ++it)
+ {
+ s << *it;
+ }
+#else
+ s << "UNAVAILABLE IN RELEASE";
+#endif
+ return s;
+}
+
+LLContextStrings::LLContextStrings()
+{
+}
+
+// static
+LLContextStrings* LLContextStrings::getThreadLocalInstance()
+{
+ LLContextStrings *cons = LLThreadLocalSingletonPointer<LLContextStrings>::getInstance();
+ if (!cons)
+ {
+ LLThreadLocalSingletonPointer<LLContextStrings>::setInstance(new LLContextStrings);
+ }
+ return LLThreadLocalSingletonPointer<LLContextStrings>::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<std::string,S32>& strings =
+ LLThreadLocalSingletonPointer<LLContextStrings>::getInstance()->m_contextStrings;
+ for (std::map<std::string,S32>::const_iterator it = strings.begin(); it!=strings.end(); ++it)
+ {
+ if (it->first.find(str) != std::string::npos)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+void LLContextStrings::output(std::ostream& os)
+{
+ const std::map<std::string,S32>& strings =
+ LLThreadLocalSingletonPointer<LLContextStrings>::getInstance()->m_contextStrings;
+ for (std::map<std::string,S32>::const_iterator it = strings.begin(); it!=strings.end(); ++it)
+ {
+ os << it->first << "[" << it->second << "]" << "\n";
+ }
+}
+
+// static
+std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status)
+{
+ LLThreadLocalSingletonPointer<LLContextStrings>::getInstance()->output(s);
+ return s;
+}
+
+bool LLContextStatus::contains(const std::string& str)
+{
+ return LLThreadLocalSingletonPointer<LLContextStrings>::getInstance()->contains(str);
+}
diff --git a/indra/llcommon/llcallstack.h b/indra/llcommon/llcallstack.h
new file mode 100644
index 0000000000..1f7a7689d7
--- /dev/null
+++ b/indra/llcommon/llcallstack.h
@@ -0,0 +1,80 @@
+/**
+ * @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 <map>
+
+class LLCallStackImpl;
+
+class LLCallStack
+{
+public:
+ LLCallStack(S32 skip_count=0, bool verbose=false);
+ std::vector<std::string> 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<std::string,S32> 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);
diff --git a/indra/llcommon/llcleanup.cpp b/indra/llcommon/llcleanup.cpp
new file mode 100644
index 0000000000..c5283507bf
--- /dev/null
+++ b/indra/llcommon/llcleanup.cpp
@@ -0,0 +1,29 @@
+/**
+ * @file llcleanup.cpp
+ * @author Nat Goodspeed
+ * @date 2016-08-30
+ * @brief Implementation for llcleanup.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llcleanup.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llerror.h"
+#include "llerrorcontrol.h"
+
+void log_subsystem_cleanup(const char* file, int line, const char* function,
+ const char* classname)
+{
+ LL_INFOS("Cleanup") << LLError::abbreviateFile(file) << "(" << line << "): "
+ << "calling " << classname << "::cleanupClass() in "
+ << function << LL_ENDL;
+}
diff --git a/indra/llcommon/llcleanup.h b/indra/llcommon/llcleanup.h
new file mode 100644
index 0000000000..a319171b5f
--- /dev/null
+++ b/indra/llcommon/llcleanup.h
@@ -0,0 +1,33 @@
+/**
+ * @file llcleanup.h
+ * @author Nat Goodspeed
+ * @date 2015-05-20
+ * @brief Mechanism for cleaning up subsystem resources
+ *
+ * $LicenseInfo:firstyear=2015&license=viewerlgpl$
+ * Copyright (c) 2015, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCLEANUP_H)
+#define LL_LLCLEANUP_H
+
+#include <boost/current_function.hpp>
+
+// Instead of directly calling SomeClass::cleanupClass(), use
+// SUBSYSTEM_CLEANUP(SomeClass);
+// This logs the call as well as performing it. That gives us a baseline
+// subsystem shutdown order against which to compare subsequent dynamic
+// shutdown schemes.
+#define SUBSYSTEM_CLEANUP(CLASSNAME) \
+ do { \
+ log_subsystem_cleanup(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, #CLASSNAME); \
+ CLASSNAME::cleanupClass(); \
+ } while (0)
+// Use ancient do { ... } while (0) macro trick to permit a block of
+// statements with the same syntax as a single statement.
+
+void log_subsystem_cleanup(const char* file, int line, const char* function,
+ const char* classname);
+
+#endif /* ! defined(LL_LLCLEANUP_H) */
diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp
index 19642b0982..439ff4e628 100644
--- a/indra/llcommon/llcommon.cpp
+++ b/indra/llcommon/llcommon.cpp
@@ -31,6 +31,7 @@
#include "llthread.h"
#include "lltrace.h"
#include "lltracethreadrecorder.h"
+#include "llcleanup.h"
//static
BOOL LLCommon::sAprInitialized = FALSE;
@@ -63,11 +64,11 @@ void LLCommon::cleanupClass()
sMasterThreadRecorder = NULL;
LLTrace::set_master_thread_recorder(NULL);
LLThreadSafeRefCount::cleanupThreadSafeRefCount();
- LLTimer::cleanupClass();
+ SUBSYSTEM_CLEANUP(LLTimer);
if (sAprInitialized)
{
ll_cleanup_apr();
sAprInitialized = FALSE;
}
- LLMemory::cleanupClass();
+ SUBSYSTEM_CLEANUP(LLMemory);
}
diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp
new file mode 100644
index 0000000000..24ed1fe0c9
--- /dev/null
+++ b/indra/llcommon/llcoro_get_id.cpp
@@ -0,0 +1,32 @@
+/**
+ * @file llcoro_get_id.cpp
+ * @author Nat Goodspeed
+ * @date 2016-09-03
+ * @brief Implementation for llcoro_get_id.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llcoro_get_id.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llcoros.h"
+
+namespace llcoro
+{
+
+id get_id()
+{
+ // An instance of Current can convert to LLCoros::CoroData*, which can
+ // implicitly convert to void*, which is an llcoro::id.
+ return LLCoros::Current();
+}
+
+} // llcoro
diff --git a/indra/llcommon/llcoro_get_id.h b/indra/llcommon/llcoro_get_id.h
new file mode 100644
index 0000000000..4c1dca6f19
--- /dev/null
+++ b/indra/llcommon/llcoro_get_id.h
@@ -0,0 +1,30 @@
+/**
+ * @file llcoro_get_id.h
+ * @author Nat Goodspeed
+ * @date 2016-09-03
+ * @brief Supplement the functionality in llcoro.h.
+ *
+ * This is broken out as a separate header file to resolve
+ * circularity: LLCoros isa LLSingleton, yet LLSingleton machinery
+ * requires llcoro::get_id().
+ *
+ * Be very suspicious of anyone else #including this header.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCORO_GET_ID_H)
+#define LL_LLCORO_GET_ID_H
+
+namespace llcoro
+{
+
+/// Get an opaque, distinct token for the running coroutine (or main).
+typedef void* id;
+id get_id();
+
+} // llcoro
+
+#endif /* ! defined(LL_LLCORO_GET_ID_H) */
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index d16bf0160b..1e1dfd2602 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -38,33 +38,81 @@
#include "llevents.h"
#include "llerror.h"
#include "stringize.h"
+#include "llexception.h"
-// do nothing, when we need nothing done
+namespace {
+void no_op() {}
+} // anonymous namespace
+
+// Do nothing, when we need nothing done. This is a static member of LLCoros
+// because CoroData is a private nested class.
void LLCoros::no_cleanup(CoroData*) {}
// CoroData for the currently-running coroutine. Use a thread_specific_ptr
// because each thread potentially has its own distinct pool of coroutines.
-// This thread_specific_ptr does NOT own the CoroData object! That's owned by
-// LLCoros::mCoros. It merely identifies it. For this reason we instantiate
-// it with a no-op cleanup function.
-boost::thread_specific_ptr<LLCoros::CoroData>
-LLCoros::sCurrentCoro(LLCoros::no_cleanup);
+LLCoros::Current::Current()
+{
+ // Use a function-static instance so this thread_specific_ptr is
+ // instantiated on demand. Since we happen to know it's consumed by
+ // LLSingleton, this is likely to happen before the runtime has finished
+ // initializing module-static data. For the same reason, we can't package
+ // this pointer in an LLSingleton.
+
+ // This thread_specific_ptr does NOT own the CoroData object! That's owned
+ // by LLCoros::mCoros. It merely identifies it. For this reason we
+ // instantiate it with a no-op cleanup function.
+ static boost::thread_specific_ptr<LLCoros::CoroData> sCurrent(LLCoros::no_cleanup);
+
+ // If this is the first time we're accessing sCurrent for the running
+ // thread, its get() will be NULL. This could be a problem, in that
+ // llcoro::get_id() would return the same (NULL) token value for the "main
+ // coroutine" in every thread, whereas what we really want is a distinct
+ // value for every distinct stack in the process. So if get() is NULL,
+ // give it a heap CoroData: this ensures that llcoro::get_id() will return
+ // distinct values.
+ // This tactic is "leaky": sCurrent explicitly does not destroy any
+ // CoroData to which it points, and we do NOT enter these "main coroutine"
+ // CoroData instances in the LLCoros::mCoros map. They are dummy entries,
+ // and they will leak at process shutdown: one CoroData per thread.
+ if (! sCurrent.get())
+ {
+ // It's tempting to provide a distinct name for each thread's "main
+ // coroutine." But as getName() has always returned the empty string
+ // to mean "not in a coroutine," empty string should suffice here --
+ // and truthfully the additional (thread-safe!) machinery to ensure
+ // uniqueness just doesn't feel worth the trouble.
+ // We use a no-op callable and a minimal stack size because, although
+ // CoroData's constructor in fact initializes its mCoro with a
+ // coroutine with that stack size, no one ever actually enters it by
+ // calling mCoro().
+ sCurrent.reset(new CoroData(0, // no prev
+ "", // not a named coroutine
+ no_op, // no-op callable
+ 1024)); // stacksize moot
+ }
+
+ mCurrent = &sCurrent;
+}
//static
LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
- CoroData* current = sCurrentCoro.get();
- if (! current)
- {
- LL_ERRS("LLCoros") << "Calling " << caller << " from non-coroutine context!" << LL_ENDL;
- }
+ CoroData* current = Current();
+ // With the dummy CoroData set in LLCoros::Current::Current(), this
+ // pointer should never be NULL.
+ llassert_always(current);
return *current;
}
//static
LLCoros::coro::self& LLCoros::get_self()
{
- return *get_CoroData("get_self()").mSelf;
+ CoroData& current = get_CoroData("get_self()");
+ if (! current.mSelf)
+ {
+ LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL;
+ }
+ return *current.mSelf;
}
//static
@@ -79,20 +127,23 @@ bool LLCoros::get_consuming()
return get_CoroData("get_consuming()").mConsuming;
}
-llcoro::Suspending::Suspending():
- mSuspended(LLCoros::sCurrentCoro.get())
+llcoro::Suspending::Suspending()
{
- // Revert mCurrentCoro to the value it had at the moment we last switched
+ LLCoros::Current current;
+ // Remember currently-running coroutine: we're about to suspend it.
+ mSuspended = current;
+ // Revert Current to the value it had at the moment we last switched
// into this coroutine.
- LLCoros::sCurrentCoro.reset(mSuspended->mPrev);
+ current.reset(mSuspended->mPrev);
}
llcoro::Suspending::~Suspending()
{
+ LLCoros::Current current;
// Okay, we're back, update our mPrev
- mSuspended->mPrev = LLCoros::sCurrentCoro.get();
- // and reinstate our sCurrentCoro.
- LLCoros::sCurrentCoro.reset(mSuspended);
+ mSuspended->mPrev = current;
+ // and reinstate our Current.
+ current.reset(mSuspended);
}
LLCoros::LLCoros():
@@ -100,7 +151,11 @@ LLCoros::LLCoros():
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
// empirically this is 64KB on Windows and Linux. Try quadrupling.
+#if ADDRESS_SIZE == 64
+ mStackSize(512*1024)
+#else
mStackSize(256*1024)
+#endif
{
// Register our cleanup() method for "mainloop" ticks
LLEventPumps::instance().obtain("mainloop").listen(
@@ -131,9 +186,9 @@ bool LLCoros::cleanup(const LLSD&)
if ((previousCount < 5) || !(previousCount % 50))
{
if (previousCount < 5)
- LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
+ LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
else
- LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;
+ LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;
}
// The erase() call will invalidate its passed iterator value --
@@ -185,9 +240,9 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
if ((previousCount < 5) || !(previousCount % 50))
{
if (previousCount < 5)
- LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
+ LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
else
- LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;
+ LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;
}
@@ -212,33 +267,43 @@ bool LLCoros::kill(const std::string& name)
std::string LLCoros::getName() const
{
- CoroData* current = sCurrentCoro.get();
- if (! current)
- {
- // not in a coroutine
- return "";
- }
- return current->mName;
+ return Current()->mName;
}
void LLCoros::setStackSize(S32 stacksize)
{
- LL_INFOS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL;
+ LL_DEBUGS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL;
mStackSize = stacksize;
}
// Top-level wrapper around caller's coroutine callable. This function accepts
-// the coroutine library's implicit coro::self& parameter and sets sCurrentSelf
-// but does not pass it down to the caller's callable.
+// the coroutine library's implicit coro::self& parameter and saves it, but
+// does not pass it down to the caller's callable.
void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable)
{
// capture the 'self' param in CoroData
data->mSelf = &self;
// run the code the caller actually wants in the coroutine
- callable();
+ try
+ {
+ callable();
+ }
+ catch (const LLContinueError&)
+ {
+ // Any uncaught exception derived from LLContinueError will be caught
+ // here and logged. This coroutine will terminate but the rest of the
+ // viewer will carry on.
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ }
+ catch (...)
+ {
+ // Any OTHER kind of uncaught exception will cause the viewer to
+ // crash, hopefully informatively.
+ CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ }
// This cleanup isn't perfectly symmetrical with the way we initially set
- // data->mPrev, but this is our last chance to reset mCurrentCoro.
- sCurrentCoro.reset(data->mPrev);
+ // data->mPrev, but this is our last chance to reset Current.
+ Current().reset(data->mPrev);
}
/*****************************************************************************
@@ -261,7 +326,7 @@ LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name,
mPrev(prev),
mName(name),
// Wrap the caller's callable in our toplevel() function so we can manage
- // sCurrentCoro appropriately at startup and shutdown of each coroutine.
+ // Current appropriately at startup and shutdown of each coroutine.
mCoro(boost::bind(toplevel, _1, this, callable), stacksize),
// don't consume events unless specifically directed
mConsuming(false),
@@ -272,13 +337,13 @@ LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name,
std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
{
std::string name(generateDistinctName(prefix));
- // pass the current value of sCurrentCoro as previous context
- CoroData* newCoro = new CoroData(sCurrentCoro.get(), name,
- callable, mStackSize);
+ Current current;
+ // pass the current value of Current as previous context
+ CoroData* newCoro = new CoroData(current, name, callable, mStackSize);
// Store it in our pointer map
mCoros.insert(name, newCoro);
// also set it as current
- sCurrentCoro.reset(newCoro);
+ current.reset(newCoro);
/* Run the coroutine until its first wait, then return here */
(newCoro->mCoro)(std::nothrow);
return name;
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index 39316ed0e6..bbe2d22af4 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -35,8 +35,10 @@
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/function.hpp>
#include <boost/thread/tss.hpp>
+#include <boost/noncopyable.hpp>
#include <string>
#include <stdexcept>
+#include "llcoro_get_id.h" // for friend declaration
// forward-declare helper class
namespace llcoro
@@ -83,6 +85,7 @@ class Suspending;
*/
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
+ LLSINGLETON(LLCoros);
public:
/// Canonical boost::dcoroutines::coroutine signature we use
typedef boost::dcoroutines::coroutine<void()> coro;
@@ -173,9 +176,8 @@ public:
class Future;
private:
- LLCoros();
- friend class LLSingleton<LLCoros>;
friend class llcoro::Suspending;
+ friend llcoro::id llcoro::get_id();
std::string generateDistinctName(const std::string& prefix) const;
bool cleanup(const LLSD&);
struct CoroData;
@@ -222,8 +224,22 @@ private:
typedef boost::ptr_map<std::string, CoroData> CoroMap;
CoroMap mCoros;
- // identify the current coroutine's CoroData
- static boost::thread_specific_ptr<LLCoros::CoroData> sCurrentCoro;
+ // Identify the current coroutine's CoroData. Use a little helper class so
+ // a caller can either use a temporary instance, or instantiate a named
+ // variable and access it multiple times.
+ class Current
+ {
+ public:
+ Current();
+
+ operator LLCoros::CoroData*() { return get(); }
+ LLCoros::CoroData* operator->() { return get(); }
+ LLCoros::CoroData* get() { return mCurrent->get(); }
+ void reset(LLCoros::CoroData* ptr) { mCurrent->reset(ptr); }
+
+ private:
+ boost::thread_specific_ptr<LLCoros::CoroData>* mCurrent;
+ };
};
namespace llcoro
@@ -231,7 +247,7 @@ namespace llcoro
/// Instantiate one of these in a block surrounding any leaf point when
/// control literally switches away from this coroutine.
-class Suspending
+class Suspending: boost::noncopyable
{
public:
Suspending();
diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp
index 0e72c175cb..0d5757effd 100644
--- a/indra/llcommon/lldependencies.cpp
+++ b/indra/llcommon/lldependencies.cpp
@@ -40,6 +40,7 @@
#include <boost/graph/topological_sort.hpp>
#include <boost/graph/exception.hpp>
// other Linden headers
+#include "llexception.h"
LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const
{
@@ -76,7 +77,7 @@ LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const
// Omit independent nodes: display only those that might contribute to
// the cycle.
describe(out, false);
- throw Cycle(out.str());
+ LLTHROW(Cycle(out.str()));
}
// A peculiarity of boost::topological_sort() is that it emits results in
// REVERSE topological order: to get the result you want, you must
diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h
index e0294e271b..db2bbab8b0 100644
--- a/indra/llcommon/lldependencies.h
+++ b/indra/llcommon/lldependencies.h
@@ -34,13 +34,13 @@
#include <vector>
#include <set>
#include <map>
-#include <stdexcept>
#include <iosfwd>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/indirect_iterator.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
+#include "llexception.h"
/*****************************************************************************
* Utilities
@@ -106,9 +106,9 @@ public:
/**
* Exception thrown by sort() if there's a cycle
*/
- struct Cycle: public std::runtime_error
+ struct Cycle: public LLException
{
- Cycle(const std::string& what): std::runtime_error(what) {}
+ Cycle(const std::string& what): LLException(what) {}
};
/**
@@ -124,8 +124,8 @@ public:
virtual std::string describe(bool full=true) const;
protected:
- typedef std::vector< std::pair<int, int> > EdgeList;
- typedef std::vector<int> VertexList;
+ typedef std::vector< std::pair<std::size_t, std::size_t> > EdgeList;
+ typedef std::vector<std::size_t> VertexList;
VertexList topo_sort(int vertices, const EdgeList& edges) const;
/**
@@ -508,7 +508,7 @@ public:
// been explicitly added. Rely on std::map rejecting a second attempt
// to insert the same key. Use the map's size() as the vertex number
// to get a distinct value for each successful insertion.
- typedef std::map<KEY, int> VertexMap;
+ typedef std::map<KEY, std::size_t> VertexMap;
VertexMap vmap;
// Nest each of these loops because !@#$%? MSVC warns us that its
// former broken behavior has finally been fixed -- and our builds
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 5ed348e13c..e6407ecf22 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -239,6 +239,14 @@ 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
@@ -252,31 +260,34 @@ namespace
// but gcc 3.3 libstc++'s implementation of demangling is broken
// and fails without.
- char* name = abi::__cxa_demangle(type.name(),
+ char* name = abi::__cxa_demangle(mangled,
abi_name_buf, &abi_name_len, &status);
// this call can realloc the abi_name_buf pointer (!)
- return name ? name : type.name();
+ return name ? name : mangled;
#elif LL_WINDOWS
// DevStudio: type_info::name() includes the text "class " at the start
static const std::string class_prefix = "class ";
-
- std::string name = type.name();
- std::string::size_type p = name.find(class_prefix);
- if (p == std::string::npos)
+ std::string name = mangled;
+ if (0 != name.compare(0, class_prefix.length(), class_prefix))
{
+ LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '"
+ << name << "'" << LL_ENDL;
return name;
}
- return name.substr(p + class_prefix.size());
+ return name.substr(class_prefix.length());
-#else
- return type.name();
+#else
+ return mangled;
#endif
}
+} // LLError
+namespace
+{
std::string functionName(const std::string& preprocessor_name)
{
#if LL_WINDOWS
@@ -363,9 +374,8 @@ namespace
class Globals : public LLSingleton<Globals>
{
+ LLSINGLETON(Globals);
public:
- Globals();
-
std::ostringstream messageStream;
bool messageStreamInUse;
@@ -438,11 +448,10 @@ namespace LLError
class Settings : public LLSingleton<Settings>
{
+ LLSINGLETON(Settings);
public:
- Settings();
-
SettingsConfigPtr getSettingsConfig();
-
+
void reset();
SettingsStoragePtr saveAndReset();
void restore(SettingsStoragePtr pSettingsStorage);
@@ -450,7 +459,7 @@ namespace LLError
private:
SettingsConfigPtr mSettingsConfig;
};
-
+
SettingsConfig::SettingsConfig()
: LLRefCount(),
mPrintLocation(false),
@@ -475,8 +484,7 @@ namespace LLError
mRecorders.clear();
}
- Settings::Settings()
- : LLSingleton<Settings>(),
+ Settings::Settings():
mSettingsConfig(new SettingsConfig())
{
}
@@ -485,26 +493,31 @@ namespace LLError
{
return mSettingsConfig;
}
-
+
void Settings::reset()
{
Globals::getInstance()->invalidateCallSites();
mSettingsConfig = new SettingsConfig();
}
-
+
SettingsStoragePtr Settings::saveAndReset()
{
SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get());
reset();
return oldSettingsConfig;
}
-
+
void Settings::restore(SettingsStoragePtr pSettingsStorage)
{
Globals::getInstance()->invalidateCallSites();
SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get()));
mSettingsConfig = newSettingsConfig;
}
+
+ bool is_available()
+ {
+ return Settings::instanceExists() && Globals::instanceExists();
+ }
}
namespace LLError
@@ -921,11 +934,6 @@ namespace
std::ostringstream message_stream;
- if (show_location && (r->wantsLocation() || level == LLError::LEVEL_ERROR || s->mPrintLocation))
- {
- message_stream << site.mLocationString << " ";
- }
-
if (show_time && r->wantsTime() && s->mTimeFunction != NULL)
{
message_stream << s->mTimeFunction() << " ";
@@ -933,17 +941,17 @@ namespace
if (show_level && r->wantsLevel())
{
- message_stream << site.mLevelString;
+ message_stream << site.mLevelString << " ";
}
if (show_tags && r->wantsTags())
{
message_stream << site.mTagString;
}
- if ((show_level && r->wantsLevel())||
- (show_tags && r->wantsTags()))
+
+ if (show_location && (r->wantsLocation() || level == LLError::LEVEL_ERROR || s->mPrintLocation))
{
- message_stream << " ";
+ message_stream << site.mLocationString << " ";
}
if (show_function && r->wantsFunctionName())
@@ -1493,3 +1501,20 @@ namespace LLError
}
}
+bool debugLoggingEnabled(const std::string& tag)
+{
+ const char* tags[] = {tag.c_str()};
+ ::size_t tag_count = 1;
+ LLError::CallSite _site(LLError::LEVEL_DEBUG, __FILE__, __LINE__,
+ typeid(_LL_CLASS_TO_LOG), __FUNCTION__, false, tags, tag_count);
+ if (LL_UNLIKELY(_site.shouldLog()))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 3beef65723..ceb1fd2c5f 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -174,7 +174,8 @@ namespace LLError
// not really a level
// used to indicate that no messages should be logged
};
-
+ // If you change ELevel, please update llvlog() macro below.
+
/* Macro support
The classes CallSite and Log are used by the logging macros below.
They are not intended for general use.
@@ -189,6 +190,7 @@ namespace LLError
static std::ostringstream* out();
static void flush(std::ostringstream* out, char* message);
static void flush(std::ostringstream*, const CallSite&);
+ static std::string demangle(const char* mangled);
};
struct LL_COMMON_API CallSite
@@ -305,24 +307,38 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
/////////////////////////////////
// Error Logging Macros
-// See top of file for common usage.
+// See top of file for common usage.
/////////////////////////////////
-// this macro uses a one-shot do statement to avoid parsing errors when writing control flow statements
-// without braces:
-// if (condition) LL_INFOS() << "True" << LL_ENDL; else LL_INFOS()() << "False" << LL_ENDL
-
-#define lllog(level, once, ...) \
- do { \
- const char* tags[] = {"", ##__VA_ARGS__}; \
- ::size_t tag_count = LL_ARRAY_SIZE(tags) - 1; \
- static LLError::CallSite _site( \
- level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), __FUNCTION__, once, &tags[1], tag_count);\
- if (LL_UNLIKELY(_site.shouldLog())) \
- { \
- std::ostringstream* _out = LLError::Log::out(); \
+// Instead of using LL_DEBUGS(), LL_INFOS() et al., it may be tempting to
+// directly code the lllog() macro so you can pass in the LLError::ELevel as a
+// variable. DON'T DO IT! The reason is that the first time control passes
+// through lllog(), it initializes a local static LLError::CallSite with that
+// *first* ELevel value. All subsequent visits will decide whether or not to
+// emit output based on the *first* ELevel value bound into that static
+// CallSite instance. Use LL_VLOGS() instead. lllog() assumes its ELevel
+// argument never varies.
+
+// this macro uses a one-shot do statement to avoid parsing errors when
+// writing control flow statements without braces:
+// if (condition) LL_INFOS() << "True" << LL_ENDL; else LL_INFOS()() << "False" << LL_ENDL;
+
+#define lllog(level, once, ...) \
+ do { \
+ const char* tags[] = {"", ##__VA_ARGS__}; \
+ static LLError::CallSite _site(lllog_site_args_(level, once, tags)); \
+ lllog_test_()
+
+#define lllog_test_() \
+ if (LL_UNLIKELY(_site.shouldLog())) \
+ { \
+ std::ostringstream* _out = LLError::Log::out(); \
(*_out)
+#define lllog_site_args_(level, once, tags) \
+ level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), \
+ __FUNCTION__, once, &tags[1], LL_ARRAY_SIZE(tags)-1
+
//Use this construct if you need to do computation in the middle of a
//message:
//
@@ -363,4 +379,49 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_INFOS_ONCE(...) lllog(LLError::LEVEL_INFO, true, ##__VA_ARGS__)
#define LL_WARNS_ONCE(...) lllog(LLError::LEVEL_WARN, true, ##__VA_ARGS__)
+// Use this if you need to pass LLError::ELevel as a variable.
+#define LL_VLOGS(level, ...) llvlog(level, false, ##__VA_ARGS__)
+#define LL_VLOGS_ONCE(level, ...) llvlog(level, true, ##__VA_ARGS__)
+
+// The problem with using lllog() with a variable level is that the first time
+// through, it initializes a static CallSite instance with whatever level you
+// pass. That first level is bound into the CallSite; the level parameter is
+// never again examined. One approach to variable level would be to
+// dynamically construct a CallSite instance every call -- which could get
+// expensive, depending on context. So instead, initialize a static CallSite
+// for each level value we support, then dynamically select the CallSite
+// instance for the passed level value.
+// Compare implementation to lllog() above.
+#define llvlog(level, once, ...) \
+ do { \
+ const char* tags[] = {"", ##__VA_ARGS__}; \
+ /* Need a static CallSite instance per expected ELevel value. */ \
+ /* Since we intend to index this array with the ELevel, */ \
+ /* _sites[0] should be ELevel(0), and so on -- avoid using */ \
+ /* ELevel symbolic names when initializing -- except for */ \
+ /* the last entry, which handles anything beyond the end. */ \
+ /* (Commented ELevel value names are from 2016-09-01.) */ \
+ /* Passing an ELevel past the end of this array is itself */ \
+ /* a fatal error, so ensure the last is LEVEL_ERROR. */ \
+ static LLError::CallSite _sites[] = \
+ { \
+ /* LEVEL_DEBUG */ \
+ LLError::CallSite(lllog_site_args_(LLError::ELevel(0), once, tags)), \
+ /* LEVEL_INFO */ \
+ LLError::CallSite(lllog_site_args_(LLError::ELevel(1), once, tags)), \
+ /* LEVEL_WARN */ \
+ LLError::CallSite(lllog_site_args_(LLError::ELevel(2), once, tags)), \
+ /* LEVEL_ERROR */ \
+ LLError::CallSite(lllog_site_args_(LLError::LEVEL_ERROR, once, tags)) \
+ }; \
+ /* Clamp the passed 'level' to at most last entry */ \
+ std::size_t which((std::size_t(level) >= LL_ARRAY_SIZE(_sites)) ? \
+ (LL_ARRAY_SIZE(_sites) - 1) : std::size_t(level)); \
+ /* selected CallSite *must* be named _site for LL_ENDL */ \
+ LLError::CallSite& _site(_sites[which]); \
+ lllog_test_()
+
+// Check at run-time whether logging is enabled, without generating output
+bool debugLoggingEnabled(const std::string& tag);
+
#endif // LL_LLERROR_H
diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h
index 56ac52e5de..56e84f7172 100644
--- a/indra/llcommon/llerrorcontrol.h
+++ b/indra/llcommon/llerrorcontrol.h
@@ -189,6 +189,11 @@ namespace LLError
LL_COMMON_API std::string abbreviateFile(const std::string& filePath);
LL_COMMON_API int shouldLogCallCount();
+
+ // Check whether Globals exists. This should only be used by LLSingleton
+ // infrastructure to avoid trying to log when our internal LLSingleton is
+ // unavailable -- circularity ensues.
+ LL_COMMON_API bool is_available();
};
#endif // LL_LLERRORCONTROL_H
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index 2d5f964deb..56367b8f54 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -39,6 +39,7 @@
#include "llerror.h"
#include "llcoros.h"
#include "llmake.h"
+#include "llexception.h"
#include "lleventfilter.h"
@@ -351,7 +352,7 @@ LLSD errorException(const LLEventWithID& result, const std::string& desc)
// returning it, deliver it via exception.
if (result.second)
{
- throw LLErrorEvent(desc, result.first);
+ LLTHROW(LLErrorEvent(desc, result.first));
}
// That way, our caller knows a simple return must be from the reply
// pump (pump 0).
diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h
index 87926c692d..84827aab4a 100644
--- a/indra/llcommon/lleventcoro.h
+++ b/indra/llcommon/lleventcoro.h
@@ -31,10 +31,10 @@
#include <boost/optional.hpp>
#include <string>
-#include <stdexcept>
#include <utility> // std::pair
#include "llevents.h"
#include "llerror.h"
+#include "llexception.h"
/**
* Like LLListenerOrPumpName, this is a class intended for parameter lists:
@@ -234,11 +234,11 @@ LLSD errorException(const LLEventWithID& result, const std::string& desc);
* because it's not an error in event processing: rather, this exception
* announces an event that bears error information (for some other API).
*/
-class LL_COMMON_API LLErrorEvent: public std::runtime_error
+class LL_COMMON_API LLErrorEvent: public LLException
{
public:
LLErrorEvent(const std::string& what, const LLSD& data):
- std::runtime_error(what),
+ LLException(what),
mData(data)
{}
virtual ~LLErrorEvent() throw() {}
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 645c29d770..97270e4931 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -57,6 +57,7 @@
#include "stringize.h"
#include "llerror.h"
#include "llsdutil.h"
+#include "llexception.h"
#if LL_MSVC
#pragma warning (disable : 4702)
#endif
@@ -174,7 +175,7 @@ std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string
// Unless we're permitted to tweak it, that's Bad.
if (! tweak)
{
- throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'");
+ LLTHROW(LLEventPump::DupPumpName("Duplicate LLEventPump name '" + name + "'"));
}
// The passed name isn't unique, but we're permitted to tweak it. Find the
// first decimal-integer suffix not already taken. The insert() attempt
@@ -275,6 +276,8 @@ LLEventPumps::~LLEventPumps()
#pragma warning (push)
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
#endif
+const std::string LLEventPump::ANONYMOUS = std::string();
+
LLEventPump::LLEventPump(const std::string& name, bool tweak):
// Register every new instance with LLEventPumps
@@ -313,145 +316,162 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
const NameList& after,
const NameList& before)
{
- // Check for duplicate name before connecting listener to mSignal
- ConnectionMap::const_iterator found = mConnections.find(name);
- // In some cases the user might disconnect a connection explicitly -- or
- // might use LLEventTrackable to disconnect implicitly. Either way, we can
- // end up retaining in mConnections a zombie connection object that's
- // already been disconnected. Such a connection object can't be
- // reconnected -- nor, in the case of LLEventTrackable, would we want to
- // try, since disconnection happens with the destruction of the listener
- // object. That means it's safe to overwrite a disconnected connection
- // object with the new one we're attempting. The case we want to prevent
- // is only when the existing connection object is still connected.
- if (found != mConnections.end() && found->second.connected())
- {
- throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name +
- "' on " + typeid(*this).name() + " '" + getName() + "'");
- }
- // Okay, name is unique, try to reconcile its dependencies. Specify a new
- // "node" value that we never use for an mSignal placement; we'll fix it
- // later.
- DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before);
- // What if this listener has been added, removed and re-added? In that
- // case newNode already has a non-negative value because we never remove a
- // listener from mDeps. But keep processing uniformly anyway in case the
- // listener was added back with different dependencies. Then mDeps.sort()
- // would put it in a different position, and the old newNode placement
- // value would be wrong, so we'd have to reassign it anyway. Trust that
- // re-adding a listener with the same dependencies is the trivial case for
- // mDeps.sort(): it can just replay its cache.
- DependencyMap::sorted_range sorted_range;
- try
- {
- // Can we pick an order that works including this new entry?
- sorted_range = mDeps.sort();
- }
- catch (const DependencyMap::Cycle& e)
- {
- // No: the new node's after/before dependencies have made mDeps
- // unsortable. If we leave the new node in mDeps, it will continue
- // to screw up all future attempts to sort()! Pull it out.
- mDeps.remove(name);
- throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() +
- " '" + getName() + "' would cause cycle: " + e.what());
- }
- // Walk the list to verify that we haven't changed the order.
- float previous = 0.0, myprev = 0.0;
- DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop
- for (DependencyMap::sorted_iterator dmi = sorted_range.begin();
- dmi != sorted_range.end(); ++dmi)
+ 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
+ // listener. We'll just tack it on to the end.
+ if (!name.empty()) // should be the same as testing against ANONYMOUS
{
- // Since we've added the new entry with an invalid placement,
- // recognize it and skip it.
- if (dmi->first == name)
+ // Check for duplicate name before connecting listener to mSignal
+ ConnectionMap::const_iterator found = mConnections.find(name);
+ // In some cases the user might disconnect a connection explicitly -- or
+ // might use LLEventTrackable to disconnect implicitly. Either way, we can
+ // end up retaining in mConnections a zombie connection object that's
+ // already been disconnected. Such a connection object can't be
+ // reconnected -- nor, in the case of LLEventTrackable, would we want to
+ // try, since disconnection happens with the destruction of the listener
+ // object. That means it's safe to overwrite a disconnected connection
+ // object with the new one we're attempting. The case we want to prevent
+ // is only when the existing connection object is still connected.
+ if (found != mConnections.end() && found->second.connected())
{
- // Remember the iterator belonging to our new node, and which
- // placement value was 'previous' at that point.
- mydmi = dmi;
- myprev = previous;
- continue;
+ LLTHROW(DupListenerName("Attempt to register duplicate listener name '" + name +
+ "' on " + typeid(*this).name() + " '" + getName() + "'"));
}
- // If the new node has rearranged the existing nodes, we'll find
- // that their placement values are no longer in increasing order.
- if (dmi->second < previous)
+ // Okay, name is unique, try to reconcile its dependencies. Specify a new
+ // "node" value that we never use for an mSignal placement; we'll fix it
+ // later.
+ DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before);
+ // What if this listener has been added, removed and re-added? In that
+ // case newNode already has a non-negative value because we never remove a
+ // listener from mDeps. But keep processing uniformly anyway in case the
+ // listener was added back with different dependencies. Then mDeps.sort()
+ // would put it in a different position, and the old newNode placement
+ // value would be wrong, so we'd have to reassign it anyway. Trust that
+ // re-adding a listener with the same dependencies is the trivial case for
+ // mDeps.sort(): it can just replay its cache.
+ DependencyMap::sorted_range sorted_range;
+ try
{
- // This is another scenario in which we'd better back out the
- // newly-added node from mDeps -- but don't do it yet, we want to
- // traverse the existing mDeps to report on it!
- // Describe the change to the order of our listeners. Copy
- // everything but the newest listener to a vector we can sort to
- // obtain the old order.
- typedef std::vector< std::pair<float, std::string> > SortNameList;
- SortNameList sortnames;
- for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end());
- cdmi != cdmend; ++cdmi)
+ // Can we pick an order that works including this new entry?
+ sorted_range = mDeps.sort();
+ }
+ catch (const DependencyMap::Cycle& e)
+ {
+ // No: the new node's after/before dependencies have made mDeps
+ // unsortable. If we leave the new node in mDeps, it will continue
+ // to screw up all future attempts to sort()! Pull it out.
+ mDeps.remove(name);
+ LLTHROW(Cycle("New listener '" + name + "' on " + typeid(*this).name() +
+ " '" + getName() + "' would cause cycle: " + e.what()));
+ }
+ // Walk the list to verify that we haven't changed the order.
+ float previous = 0.0, myprev = 0.0;
+ DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop
+ for (DependencyMap::sorted_iterator dmi = sorted_range.begin();
+ dmi != sorted_range.end(); ++dmi)
+ {
+ // Since we've added the new entry with an invalid placement,
+ // recognize it and skip it.
+ if (dmi->first == name)
{
- if (cdmi->first != name)
- {
- sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first));
- }
+ // Remember the iterator belonging to our new node, and which
+ // placement value was 'previous' at that point.
+ mydmi = dmi;
+ myprev = previous;
+ continue;
}
- std::sort(sortnames.begin(), sortnames.end());
- std::ostringstream out;
- out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName()
- << "' would move previous listener '" << dmi->first << "'\nwas: ";
- SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end());
- if (sni != snend)
+ // If the new node has rearranged the existing nodes, we'll find
+ // that their placement values are no longer in increasing order.
+ if (dmi->second < previous)
{
- out << sni->second;
- while (++sni != snend)
+ // This is another scenario in which we'd better back out the
+ // newly-added node from mDeps -- but don't do it yet, we want to
+ // traverse the existing mDeps to report on it!
+ // Describe the change to the order of our listeners. Copy
+ // everything but the newest listener to a vector we can sort to
+ // obtain the old order.
+ typedef std::vector< std::pair<float, std::string> > SortNameList;
+ SortNameList sortnames;
+ for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end());
+ cdmi != cdmend; ++cdmi)
{
- out << ", " << sni->second;
+ if (cdmi->first != name)
+ {
+ sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first));
+ }
}
- }
- out << "\nnow: ";
- DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end());
- if (ddmi != ddmend)
- {
- out << ddmi->first;
- while (++ddmi != ddmend)
+ std::sort(sortnames.begin(), sortnames.end());
+ std::ostringstream out;
+ out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName()
+ << "' would move previous listener '" << dmi->first << "'\nwas: ";
+ SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end());
+ if (sni != snend)
{
- out << ", " << ddmi->first;
+ out << sni->second;
+ while (++sni != snend)
+ {
+ out << ", " << sni->second;
+ }
}
+ out << "\nnow: ";
+ DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end());
+ if (ddmi != ddmend)
+ {
+ out << ddmi->first;
+ while (++ddmi != ddmend)
+ {
+ out << ", " << ddmi->first;
+ }
+ }
+ // NOW remove the offending listener node.
+ mDeps.remove(name);
+ // Having constructed a description of the order change, inform caller.
+ LLTHROW(OrderChange(out.str()));
}
- // NOW remove the offending listener node.
- mDeps.remove(name);
- // Having constructed a description of the order change, inform caller.
- throw OrderChange(out.str());
+ // This node becomes the previous one.
+ previous = dmi->second;
}
- // This node becomes the previous one.
- previous = dmi->second;
- }
- // We just got done with a successful mDeps.add(name, ...) call. We'd
- // better have found 'name' somewhere in that sorted list!
- assert(mydmi != sorted_range.end());
- // Four cases:
- // 0. name is the only entry: placement 1.0
- // 1. name is the first of several entries: placement (next placement)/2
- // 2. name is between two other entries: placement (myprev + (next placement))/2
- // 3. name is the last entry: placement ceil(myprev) + 1.0
- // Since we've cleverly arranged for myprev to be 0.0 if name is the
- // first entry, this folds down to two cases. Case 1 is subsumed by
- // case 2, and case 0 is subsumed by case 3. So we need only handle
- // cases 2 and 3, which means we need only detect whether name is the
- // last entry. Increment mydmi to see if there's anything beyond.
- if (++mydmi != sorted_range.end())
- {
- // The new node isn't last. Place it between the previous node and
- // the successor.
- newNode = (myprev + mydmi->second)/2.f;
- }
- else
- {
- // The new node is last. Bump myprev up to the next integer, add
- // 1.0 and use that.
- newNode = std::ceil(myprev) + 1.f;
+ // We just got done with a successful mDeps.add(name, ...) call. We'd
+ // better have found 'name' somewhere in that sorted list!
+ assert(mydmi != sorted_range.end());
+ // Four cases:
+ // 0. name is the only entry: placement 1.0
+ // 1. name is the first of several entries: placement (next placement)/2
+ // 2. name is between two other entries: placement (myprev + (next placement))/2
+ // 3. name is the last entry: placement ceil(myprev) + 1.0
+ // Since we've cleverly arranged for myprev to be 0.0 if name is the
+ // first entry, this folds down to two cases. Case 1 is subsumed by
+ // case 2, and case 0 is subsumed by case 3. So we need only handle
+ // cases 2 and 3, which means we need only detect whether name is the
+ // last entry. Increment mydmi to see if there's anything beyond.
+ if (++mydmi != sorted_range.end())
+ {
+ // The new node isn't last. Place it between the previous node and
+ // the successor.
+ newNode = (myprev + mydmi->second) / 2.f;
+ }
+ else
+ {
+ // The new node is last. Bump myprev up to the next integer, add
+ // 1.0 and use that.
+ newNode = std::ceil(myprev) + 1.f;
+ }
+
+ nodePosition = newNode;
}
// Now that newNode has a value that places it appropriately in mSignal,
// connect it.
- LLBoundListener bound = mSignal->connect(newNode, listener);
- mConnections[name] = bound;
+ 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.
+ mConnections[name] = bound;
+ }
return bound;
}
@@ -608,7 +628,7 @@ bool LLListenerOrPumpName::operator()(const LLSD& event) const
{
if (! mListener)
{
- throw Empty("attempting to call uninitialized");
+ LLTHROW(Empty("attempting to call uninitialized"));
}
return (*mListener)(event);
}
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index ba4fcd766e..7cff7dfd45 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -37,7 +37,6 @@
#include <set>
#include <vector>
#include <deque>
-#include <stdexcept>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@@ -62,6 +61,7 @@
#include "llsingleton.h"
#include "lldependencies.h"
#include "llstl.h"
+#include "llexception.h"
/*==========================================================================*|
// override this to allow binding free functions with more parameters
@@ -95,12 +95,32 @@ struct LLStopWhenHandled
result_type operator()(InputIterator first, InputIterator last) const
{
for (InputIterator si = first; si != last; ++si)
- {
- if (*si)
- {
- return true;
- }
- }
+ {
+ try
+ {
+ if (*si)
+ {
+ return true;
+ }
+ }
+ catch (const LLContinueError&)
+ {
+ // We catch LLContinueError here because an LLContinueError-
+ // based exception means the viewer as a whole should carry on
+ // to the best of our ability. Therefore subsequent listeners
+ // on the same LLEventPump should still receive this event.
+
+ // The iterator passed to a boost::signals2 Combiner is very
+ // clever, but provides no contextual information. We would
+ // very much like to be able to log the name of the LLEventPump
+ // plus the name of this particular listener, but alas.
+ LOG_UNHANDLED_EXCEPTION("LLEventPump");
+ }
+ // We do NOT catch (...) here because we might as well let it
+ // propagate out to the generic handler. If we were able to log
+ // context information here, that would be great, but we can't, so
+ // there's no point.
+ }
return false;
}
};
@@ -188,10 +208,10 @@ public:
bool operator()(const LLSD& event) const;
/// exception if you try to call when empty
- struct Empty: public std::runtime_error
+ struct Empty: public LLException
{
Empty(const std::string& what):
- std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {}
+ LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}
};
private:
@@ -209,7 +229,7 @@ class LLEventPump;
*/
class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps>
{
- friend class LLSingleton<LLEventPumps>;
+ LLSINGLETON(LLEventPumps);
public:
/**
* Find or create an LLEventPump instance with a specific name. We return
@@ -252,7 +272,6 @@ private:
void unregister(const LLEventPump&);
private:
- LLEventPumps();
~LLEventPumps();
testable:
@@ -365,16 +384,18 @@ typedef boost::signals2::trackable LLEventTrackable;
class LL_COMMON_API LLEventPump: public LLEventTrackable
{
public:
+ static const std::string ANONYMOUS; // constant for anonymous listeners.
+
/**
* Exception thrown by LLEventPump(). You are trying to instantiate an
* LLEventPump (subclass) using the same name as some other instance, and
* you didn't pass <tt>tweak=true</tt> to permit it to generate a unique
* variant.
*/
- struct DupPumpName: public std::runtime_error
+ struct DupPumpName: public LLException
{
DupPumpName(const std::string& what):
- std::runtime_error(std::string("DupPumpName: ") + what) {}
+ LLException(std::string("DupPumpName: ") + what) {}
};
/**
@@ -399,9 +420,9 @@ public:
/// group exceptions thrown by listen(). We use exceptions because these
/// particular errors are likely to be coding errors, found and fixed by
/// the developer even before preliminary checkin.
- struct ListenError: public std::runtime_error
+ struct ListenError: public LLException
{
- ListenError(const std::string& what): std::runtime_error(what) {}
+ ListenError(const std::string& what): LLException(what) {}
};
/**
* exception thrown by listen(). You are attempting to register a
@@ -476,6 +497,12 @@ public:
* instantiate your listener, then passing the same name on each listen()
* call, allows us to optimize away the second and subsequent dependency
* sorts.
+ *
+ * If name is set to LLEventPump::ANONYMOUS listen will bypass the entire
+ * dependency and ordering calculation. In this case, it is critical that
+ * the result be assigned to a LLTempBoundListener or the listener is
+ * manually disconnected when no longer needed since there will be no
+ * way to later find and disconnect this listener manually.
*
* If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a
* listener, listen() will inspect the components of that expression. If a
diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp
new file mode 100644
index 0000000000..b32ec2c9c9
--- /dev/null
+++ b/indra/llcommon/llexception.cpp
@@ -0,0 +1,55 @@
+/**
+ * @file llexception.cpp
+ * @author Nat Goodspeed
+ * @date 2016-08-12
+ * @brief Implementation for llexception.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llexception.h"
+// STL headers
+// std headers
+#include <typeinfo>
+// external library headers
+#include <boost/exception/diagnostic_information.hpp>
+// other Linden headers
+#include "llerror.h"
+#include "llerrorcontrol.h"
+
+namespace {
+// used by crash_on_unhandled_exception_() and log_unhandled_exception_()
+void log_unhandled_exception_(LLError::ELevel level,
+ const char* file, int line, const char* pretty_function,
+ const std::string& context)
+{
+ // log same message but allow caller-specified severity level
+ LL_VLOGS(level, "LLException") << LLError::abbreviateFile(file)
+ << "(" << line << "): Unhandled exception caught in " << pretty_function;
+ if (! context.empty())
+ {
+ LL_CONT << ": " << context;
+ }
+ LL_CONT << ":\n" << boost::current_exception_diagnostic_information() << LL_ENDL;
+}
+}
+
+void crash_on_unhandled_exception_(const char* file, int line, const char* pretty_function,
+ const std::string& context)
+{
+ // LL_ERRS() terminates and propagates message into crash dump.
+ log_unhandled_exception_(LLError::LEVEL_ERROR, file, line, pretty_function, context);
+}
+
+void log_unhandled_exception_(const char* file, int line, const char* pretty_function,
+ const std::string& context)
+{
+ // Use LL_WARNS() because we seriously do not expect this to happen
+ // routinely, but we DO expect to return from this function.
+ log_unhandled_exception_(LLError::LEVEL_WARN, file, line, pretty_function, context);
+}
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
new file mode 100644
index 0000000000..dfcb7c192f
--- /dev/null
+++ b/indra/llcommon/llexception.h
@@ -0,0 +1,85 @@
+/**
+ * @file llexception.h
+ * @author Nat Goodspeed
+ * @date 2016-06-29
+ * @brief Types needed for generic exception handling
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLEXCEPTION_H)
+#define LL_LLEXCEPTION_H
+
+#include <stdexcept>
+#include <boost/exception/exception.hpp>
+#include <boost/throw_exception.hpp>
+#include <boost/current_function.hpp>
+
+// "Found someone who can comfort me
+// But there are always exceptions..."
+// - Empty Pages, Traffic, from John Barleycorn (1970)
+// https://www.youtube.com/watch?v=dRH0CGVK7ic
+
+/**
+ * LLException is intended as the common base class from which all
+ * viewer-specific exceptions are derived. Rationale for why it's derived from
+ * both std::exception and boost::exception is explained in
+ * tests/llexception_test.cpp.
+ *
+ * boost::current_exception_diagnostic_information() is quite wonderful: if
+ * all we need to do with an exception is log it, in most places we should
+ * catch (...) and log boost::current_exception_diagnostic_information().
+ * See CRASH_ON_UNHANDLED_EXCEPTION() and LOG_UNHANDLED_EXCEPTION() below.
+ *
+ * There may be circumstances in which it would be valuable to distinguish an
+ * exception explicitly thrown by viewer code from an exception thrown by
+ * (say) a third-party library. Catching (const LLException&) supports such
+ * usage. However, most of the value of this base class is in the
+ * diagnostic_information() available via Boost.Exception.
+ */
+struct LLException:
+ public std::runtime_error,
+ public boost::exception
+{
+ LLException(const std::string& what):
+ std::runtime_error(what)
+ {}
+};
+
+/**
+ * The point of LLContinueError is to distinguish exceptions that need not
+ * terminate the whole viewer session. In general, an uncaught exception will
+ * be logged and will crash the viewer. However, though an uncaught exception
+ * derived from LLContinueError will still be logged, the viewer will attempt
+ * to continue processing.
+ */
+struct LLContinueError: public LLException
+{
+ LLContinueError(const std::string& what):
+ LLException(what)
+ {}
+};
+
+/**
+ * Please use LLTHROW() to throw viewer exceptions whenever possible. This
+ * enriches the exception's diagnostic_information() with the source file,
+ * line and containing function of the LLTHROW() macro.
+ */
+// Currently we implement that using BOOST_THROW_EXCEPTION(). Wrap it in
+// LLTHROW() in case we ever want to revisit that implementation decision.
+#define LLTHROW(x) BOOST_THROW_EXCEPTION(x)
+
+/// Call this macro from a catch (...) clause
+#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \
+ crash_on_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)
+void crash_on_unhandled_exception_(const char*, int, const char*, const std::string&);
+
+/// Call this from a catch (const LLContinueError&) clause, or from a catch
+/// (...) clause in which you do NOT want the viewer to crash.
+#define LOG_UNHANDLED_EXCEPTION(CONTEXT) \
+ log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)
+void log_unhandled_exception_(const char*, int, const char*, const std::string&);
+
+#endif /* ! defined(LL_LLEXCEPTION_H) */
diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h
index 2370253078..2024d707da 100644
--- a/indra/llcommon/llfasttimer.h
+++ b/indra/llcommon/llfasttimer.h
@@ -90,33 +90,15 @@ public:
#if LL_FASTTIMER_USE_RDTSC
static U32 getCPUClockCount32()
{
- U32 ret_val;
- __asm
- {
- _emit 0x0f
- _emit 0x31
- shr eax,8
- shl edx,24
- or eax, edx
- mov dword ptr [ret_val], eax
- }
- return ret_val;
+ unsigned __int64 val = __rdtsc();
+ val = val >> 8;
+ return static_cast<U32>(val);
}
// return full timer value, *not* shifted by 8 bits
static U64 getCPUClockCount64()
{
- U64 ret_val;
- __asm
- {
- _emit 0x0f
- _emit 0x31
- mov eax,eax
- mov edx,edx
- mov dword ptr [ret_val+4], edx
- mov dword ptr [ret_val], eax
- }
- return ret_val;
+ return static_cast<U64>( __rdtsc() );
}
#else
@@ -173,16 +155,16 @@ public:
// Mac+Linux+Solaris FAST x86 implementation of CPU clock
static U32 getCPUClockCount32()
{
- U64 x;
- __asm__ volatile (".byte 0x0f, 0x31": "=A"(x));
- return (U32)(x >> 8);
+ U32 low(0),high(0);
+ __asm__ volatile (".byte 0x0f, 0x31": "=a"(low), "=d"(high) );
+ return (low>>8) | (high<<24);
}
static U64 getCPUClockCount64()
{
- U64 x;
- __asm__ volatile (".byte 0x0f, 0x31": "=A"(x));
- return x;
+ U32 low(0),high(0);
+ __asm__ volatile (".byte 0x0f, 0x31": "=a"(low), "=d"(high) );
+ return (U64)low | ( ((U64)high) << 32);
}
#endif
@@ -296,7 +278,16 @@ LL_FORCE_INLINE BlockTimer::BlockTimer(BlockTimerStatHandle& timer)
{
#if LL_FAST_TIMER_ON
BlockTimerStackRecord* cur_timer_data = LLThreadLocalSingletonPointer<BlockTimerStackRecord>::getInstance();
- if (!cur_timer_data) return;
+ if (!cur_timer_data)
+ {
+ // How likely is it that
+ // LLThreadLocalSingletonPointer<T>::getInstance() will return NULL?
+ // Even without researching, what we can say is that if we exit
+ // without setting mStartTime at all, gcc 4.7 produces (fatal)
+ // warnings about a possibly-uninitialized data member.
+ mStartTime = 0;
+ return;
+ }
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
accumulator.mActiveCount++;
// keep current parent as long as it is active when we are
diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp
index 873a7bce25..7b559861bb 100644
--- a/indra/llcommon/llfile.cpp
+++ b/indra/llcommon/llfile.cpp
@@ -239,7 +239,7 @@ int LLFile::close(LLFILE * file)
}
-int LLFile::remove(const std::string& filename)
+int LLFile::remove(const std::string& filename, int supress_error)
{
#if LL_WINDOWS
std::string utf8filename = filename;
@@ -248,7 +248,7 @@ int LLFile::remove(const std::string& filename)
#else
int rc = ::remove(filename.c_str());
#endif
- return warnif("remove", filename, rc);
+ return warnif("remove", filename, rc, supress_error);
}
int LLFile::rename(const std::string& filename, const std::string& newname)
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 3e25228aeb..37eb75881c 100644
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -45,7 +45,7 @@ typedef FILE LLFILE;
typedef struct _stat llstat;
#else
typedef struct stat llstat;
-#include <bits/postypes.h>
+#include <sys/types.h>
#endif
#ifndef S_ISREG
@@ -72,7 +72,7 @@ public:
static int mkdir(const std::string& filename, int perms = 0700);
static int rmdir(const std::string& filename);
- static int remove(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);
static bool copy(const std::string from, const std::string to);
diff --git a/indra/llcommon/llhandle.h b/indra/llcommon/llhandle.h
index 401e4d759a..feb5f41848 100644
--- a/indra/llcommon/llhandle.h
+++ b/indra/llcommon/llhandle.h
@@ -28,8 +28,11 @@
#define LLHANDLE_H
#include "llpointer.h"
+#include "llexception.h"
+#include <stdexcept>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/utility/enable_if.hpp>
+#include <boost/throw_exception.hpp>
/**
* Helper object for LLHandle. Don't instantiate these directly, used
@@ -213,4 +216,82 @@ private:
mutable LLRootHandle<T> mHandle;
};
+
+
+class LLCheckedHandleBase
+{
+public:
+ class Stale : public LLException
+ {
+ public:
+ Stale() :
+ LLException("Attempt to access stale handle.")
+ {}
+ };
+
+protected:
+ LLCheckedHandleBase() { }
+
+};
+
+/**
+ * This is a simple wrapper for Handles, allowing direct calls to the underlying
+ * pointer. The checked handle will throw a Stale if an attempt
+ * is made to access the object referenced by the handle and that object has
+ * been destroyed.
+ **/
+template <typename T>
+class LLCheckedHandle: public LLCheckedHandleBase
+{
+public:
+
+ LLCheckedHandle(LLHandle<T> handle):
+ mHandle(handle)
+ { }
+
+ /**
+ * Test the underlying handle. If it is no longer valid, throw a Stale exception.
+ */
+ void check() const
+ {
+ T* ptr = mHandle.get();
+ if (!ptr)
+ BOOST_THROW_EXCEPTION(Stale());
+ }
+
+ /**
+ * Cast back to an appropriate handle
+ */
+ operator LLHandle<T>() const
+ {
+ return mHandle;
+ }
+
+ /**
+ * Converts the LLCheckedHandle to a bool. Allows for if (chkdHandle) {}
+ * Does not throw.
+ */
+ /*explicit*/ operator bool() const // explicit conversion operator not available with Linux compiler
+ {
+ return (mHandle.get() != NULL);
+ }
+
+ /**
+ * Attempt to call a method or access a member in the structure referenced
+ * by the handle. If the handle no longer points to a valid structure
+ * throw a Stale.
+ */
+ T* operator ->() const
+ {
+ T* ptr = mHandle.get();
+ if (!ptr)
+ BOOST_THROW_EXCEPTION(Stale());
+ return ptr;
+ }
+
+private:
+
+ LLHandle<T> mHandle;
+};
+
#endif
diff --git a/indra/llcommon/llheteromap.cpp b/indra/llcommon/llheteromap.cpp
new file mode 100644
index 0000000000..7c19196e0c
--- /dev/null
+++ b/indra/llcommon/llheteromap.cpp
@@ -0,0 +1,32 @@
+/**
+ * @file llheteromap.cpp
+ * @author Nat Goodspeed
+ * @date 2016-10-12
+ * @brief Implementation for llheteromap.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llheteromap.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+LLHeteroMap::~LLHeteroMap()
+{
+ // For each entry in our map, we must call its deleter, which is the only
+ // record we have of its original type.
+ for (TypeMap::iterator mi(mMap.begin()), me(mMap.end()); mi != me; ++mi)
+ {
+ // mi->second is the std::pair; mi->second.first is the void*;
+ // mi->second.second points to the deleter function
+ (mi->second.second)(mi->second.first);
+ mi->second.first = NULL;
+ }
+}
diff --git a/indra/llcommon/llheteromap.h b/indra/llcommon/llheteromap.h
new file mode 100644
index 0000000000..7e96172333
--- /dev/null
+++ b/indra/llcommon/llheteromap.h
@@ -0,0 +1,95 @@
+/**
+ * @file llheteromap.h
+ * @author Nat Goodspeed
+ * @date 2016-10-12
+ * @brief Map capable of storing objects of diverse types, looked up by type.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLHETEROMAP_H)
+#define LL_LLHETEROMAP_H
+
+#include <typeinfo>
+#include <utility> // std::pair
+#include <map>
+
+/**
+ * LLHeteroMap addresses an odd requirement. Usually when you want to put
+ * objects of different classes into a runtime collection of any kind, you
+ * derive them all from a common base class and store pointers to that common
+ * base class.
+ *
+ * LLInitParam::BaseBlock uses disturbing raw-pointer arithmetic to find data
+ * members in its subclasses. It seems that no BaseBlock subclass can be
+ * stored in a polymorphic class of any kind: the presence of a vtbl pointer
+ * in the layout silently throws off the reinterpret_cast arithmetic. Bad
+ * Things result. (Many thanks to Nicky D for this analysis!)
+ *
+ * LLHeteroMap collects objects WITHOUT a common base class, retrieves them by
+ * object type and destroys them when the LLHeteroMap is destroyed.
+ */
+class LLHeteroMap
+{
+public:
+ ~LLHeteroMap();
+
+ /// find or create
+ template <class T>
+ T& obtain()
+ {
+ // Look up map entry by typeid(T). We don't simply use mMap[typeid(T)]
+ // because that requires default-constructing T on every lookup. For
+ // some kinds of T, that could be expensive.
+ TypeMap::iterator found = mMap.find(&typeid(T));
+ if (found == mMap.end())
+ {
+ // Didn't find typeid(T). Create an entry. Because we're storing
+ // only a void* in the map, discarding type information, make sure
+ // we capture that type information in our deleter.
+ void* ptr = new T();
+ void (*dlfn)(void*) = &deleter<T>;
+ std::pair<TypeMap::iterator, bool> inserted =
+ mMap.insert(TypeMap::value_type(&typeid(T),
+ TypeMap::mapped_type(ptr, dlfn)));
+ // Okay, now that we have an entry, claim we found it.
+ found = inserted.first;
+ }
+ // found->second is the std::pair; second.first is the void*
+ // pointer to the object in question. Cast it to correct type and
+ // dereference it.
+ return *(static_cast<T*>(found->second.first));
+ }
+
+private:
+ template <class T>
+ static
+ void deleter(void* p)
+ {
+ delete static_cast<T*>(p);
+ }
+
+ // Comparing two std::type_info* values is tricky, because the standard
+ // does not guarantee that there will be only one type_info instance for a
+ // given type. In other words, &typeid(A) in one part of the program may
+ // not always equal &typeid(A) in some other part. Use special comparator.
+ struct type_info_ptr_comp
+ {
+ bool operator()(const std::type_info* lhs, const std::type_info* rhs) const
+ {
+ return lhs->before(*rhs);
+ }
+ };
+
+ // What we actually store is a map from std::type_info (permitting lookup
+ // by object type) to a void* pointer to the object PLUS its deleter.
+ typedef std::map<
+ const std::type_info*, std::pair<void*, void (*)(void*)>,
+ type_info_ptr_comp>
+ TypeMap;
+ TypeMap mMap;
+};
+
+#endif /* ! defined(LL_LLHETEROMAP_H) */
diff --git a/indra/llcommon/llinitdestroyclass.cpp b/indra/llcommon/llinitdestroyclass.cpp
new file mode 100644
index 0000000000..e6382a7924
--- /dev/null
+++ b/indra/llcommon/llinitdestroyclass.cpp
@@ -0,0 +1,30 @@
+/**
+ * @file llinitdestroyclass.cpp
+ * @author Nat Goodspeed
+ * @date 2016-08-30
+ * @brief Implementation for llinitdestroyclass.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llinitdestroyclass.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llerror.h"
+
+void LLCallbackRegistry::fireCallbacks() const
+{
+ for (FuncList::const_iterator fi = mCallbacks.begin(), fe = mCallbacks.end();
+ fi != fe; ++fi)
+ {
+ LL_INFOS("LLInitDestroyClass") << "calling " << fi->first << "()" << LL_ENDL;
+ fi->second();
+ }
+}
diff --git a/indra/llcommon/llinitdestroyclass.h b/indra/llcommon/llinitdestroyclass.h
new file mode 100644
index 0000000000..5f979614fe
--- /dev/null
+++ b/indra/llcommon/llinitdestroyclass.h
@@ -0,0 +1,175 @@
+/**
+ * @file llinitdestroyclass.h
+ * @author Nat Goodspeed
+ * @date 2015-05-27
+ * @brief LLInitClass / LLDestroyClass mechanism
+ *
+ * The LLInitClass template, extracted from llui.h, ensures that control will
+ * reach a static initClass() method. LLDestroyClass does the same for a
+ * static destroyClass() method.
+ *
+ * The distinguishing characteristics of these templates are:
+ *
+ * - All LLInitClass<T>::initClass() methods are triggered by an explicit call
+ * to LLInitClassList::instance().fireCallbacks(). Presumably this call
+ * happens sometime after all static objects in the program have been
+ * initialized. In other words, each LLInitClass<T>::initClass() method
+ * should be able to make some assumptions about global program state.
+ *
+ * - Similarly, LLDestroyClass<T>::destroyClass() methods are triggered by
+ * LLDestroyClassList::instance().fireCallbacks(). Again, presumably this
+ * happens at a well-defined moment in the program's shutdown sequence.
+ *
+ * - The initClass() calls happen in an unspecified sequence. You may not rely
+ * on the relative ordering of LLInitClass<T>::initClass() versus another
+ * LLInitClass<U>::initClass() method. If you need such a guarantee, use
+ * LLSingleton instead and make the dependency explicit.
+ *
+ * - Similarly, LLDestroyClass<T>::destroyClass() may happen either before or
+ * after LLDestroyClass<U>::destroyClass(). You cannot rely on that order.
+ *
+ * $LicenseInfo:firstyear=2015&license=viewerlgpl$
+ * Copyright (c) 2015, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLINITDESTROYCLASS_H)
+#define LL_LLINITDESTROYCLASS_H
+
+#include "llsingleton.h"
+#include <boost/function.hpp>
+#include <typeinfo>
+#include <vector>
+#include <utility> // std::pair
+
+/**
+ * LLCallbackRegistry is an implementation detail base class for
+ * LLInitClassList and LLDestroyClassList. It accumulates the initClass() or
+ * destroyClass() callbacks for registered classes.
+ */
+class LLCallbackRegistry
+{
+public:
+ typedef boost::function<void()> func_t;
+
+ void registerCallback(const std::string& name, const func_t& func)
+ {
+ mCallbacks.push_back(FuncList::value_type(name, func));
+ }
+
+ void fireCallbacks() const;
+
+private:
+ // Arguably this should be a boost::signals2::signal, which is, after all,
+ // a sequence of callables. We manage it by hand so we can log a name for
+ // each registered function we call.
+ typedef std::vector< std::pair<std::string, func_t> > FuncList;
+ FuncList mCallbacks;
+};
+
+/**
+ * LLInitClassList is the LLCallbackRegistry for LLInitClass. It stores the
+ * registered initClass() methods. It must be an LLSingleton because
+ * LLInitClass registers its initClass() method at static construction time
+ * (before main()), requiring LLInitClassList to be fully constructed on
+ * demand regardless of module initialization order.
+ */
+class LLInitClassList :
+ public LLCallbackRegistry,
+ public LLSingleton<LLInitClassList>
+{
+ LLSINGLETON_EMPTY_CTOR(LLInitClassList);
+};
+
+/**
+ * LLDestroyClassList is the LLCallbackRegistry for LLDestroyClass. It stores
+ * the registered destroyClass() methods. It must be an LLSingleton because
+ * LLDestroyClass registers its destroyClass() method at static construction
+ * time (before main()), requiring LLDestroyClassList to be fully constructed
+ * on demand regardless of module initialization order.
+ */
+class LLDestroyClassList :
+ public LLCallbackRegistry,
+ public LLSingleton<LLDestroyClassList>
+{
+ LLSINGLETON_EMPTY_CTOR(LLDestroyClassList);
+};
+
+/**
+ * LLRegisterWith is an implementation detail for LLInitClass and
+ * LLDestroyClass. It is intended to be used as a static class member whose
+ * constructor registers the specified callback with the LLMumbleClassList
+ * singleton registry specified as the template argument.
+ */
+template<typename T>
+class LLRegisterWith
+{
+public:
+ LLRegisterWith(const std::string& name, const LLCallbackRegistry::func_t& func)
+ {
+ T::instance().registerCallback(name, func);
+ }
+
+ // this avoids a MSVC bug where non-referenced static members are "optimized" away
+ // even if their constructors have side effects
+ S32 reference()
+ {
+ S32 dummy;
+ dummy = 0;
+ return dummy;
+ }
+};
+
+/**
+ * Derive MyClass from LLInitClass<MyClass> (the Curiously Recurring Template
+ * Pattern) to ensure that the static method MyClass::initClass() will be
+ * called (along with all other LLInitClass<T> subclass initClass() methods)
+ * when someone calls LLInitClassList::instance().fireCallbacks(). This gives
+ * the application specific control over the timing of all such
+ * initializations, without having to insert calls for every such class into
+ * generic application code.
+ */
+template<typename T>
+class LLInitClass
+{
+public:
+ LLInitClass() { sRegister.reference(); }
+
+ // When this static member is initialized, the subclass initClass() method
+ // is registered on LLInitClassList. See sRegister definition below.
+ static LLRegisterWith<LLInitClassList> sRegister;
+};
+
+/**
+ * Derive MyClass from LLDestroyClass<MyClass> (the Curiously Recurring
+ * Template Pattern) to ensure that the static method MyClass::destroyClass()
+ * will be called (along with other LLDestroyClass<T> subclass destroyClass()
+ * methods) when someone calls LLDestroyClassList::instance().fireCallbacks().
+ * This gives the application specific control over the timing of all such
+ * cleanup calls, without having to insert calls for every such class into
+ * generic application code.
+ */
+template<typename T>
+class LLDestroyClass
+{
+public:
+ LLDestroyClass() { sRegister.reference(); }
+
+ // When this static member is initialized, the subclass destroyClass()
+ // method is registered on LLInitClassList. See sRegister definition
+ // below.
+ static LLRegisterWith<LLDestroyClassList> sRegister;
+};
+
+// Here's where LLInitClass<T> specifies the subclass initClass() method.
+template <typename T>
+LLRegisterWith<LLInitClassList>
+LLInitClass<T>::sRegister(std::string(typeid(T).name()) + "::initClass",
+ &T::initClass);
+// Here's where LLDestroyClass<T> specifies the subclass destroyClass() method.
+template <typename T>
+LLRegisterWith<LLDestroyClassList>
+LLDestroyClass<T>::sRegister(std::string(typeid(T).name()) + "::destroyClass",
+ &T::destroyClass);
+
+#endif /* ! defined(LL_LLINITDESTROYCLASS_H) */
diff --git a/indra/llcommon/llinitparam.cpp b/indra/llcommon/llinitparam.cpp
index aa2f4eb289..1d104cf55d 100644
--- a/indra/llcommon/llinitparam.cpp
+++ b/indra/llcommon/llinitparam.cpp
@@ -193,7 +193,12 @@ namespace LLInitParam
{
if (!silent)
{
- p.parserWarning(llformat("Failed to parse parameter \"%s\"", p.getCurrentElementName().c_str()));
+ std::string file_name = p.getCurrentFileName();
+ if(!file_name.empty())
+ {
+ file_name = "in file: " + file_name;
+ }
+ p.parserWarning(llformat("Failed to parse parameter \"%s\" %s", p.getCurrentElementName().c_str(), file_name.c_str()));
}
return false;
}
diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h
index c65b05f610..f1f4226c40 100644
--- a/indra/llcommon/llinitparam.h
+++ b/indra/llcommon/llinitparam.h
@@ -551,6 +551,7 @@ namespace LLInitParam
}
virtual std::string getCurrentElementName() = 0;
+ virtual std::string getCurrentFileName() = 0;
virtual void parserWarning(const std::string& message);
virtual void parserError(const std::string& message);
void setParseSilently(bool silent) { mParseSilently = silent; }
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index 84d2a12f65..c87d2a3e58 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -33,6 +33,7 @@
#include "lltimer.h"
#include "lluuid.h"
#include "llleaplistener.h"
+#include "llexception.h"
#if LL_MSVC
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
@@ -69,7 +70,7 @@ public:
// Rule out empty vector
if (plugin.empty())
{
- throw Error("no plugin command");
+ LLTHROW(Error("no plugin command"));
}
// Don't leave desc empty either, but in this case, if we weren't
@@ -112,7 +113,7 @@ public:
// If that didn't work, no point in keeping this LLLeap object.
if (! mChild)
{
- throw Error(STRINGIZE("failed to run " << mDesc));
+ LLTHROW(Error(STRINGIZE("failed to run " << mDesc)));
}
// Okay, launch apparently worked. Change our mDonePump listener.
diff --git a/indra/llcommon/llleap.h b/indra/llcommon/llleap.h
index e33f25e530..8aac8a64c5 100644
--- a/indra/llcommon/llleap.h
+++ b/indra/llcommon/llleap.h
@@ -13,9 +13,9 @@
#define LL_LLLEAP_H
#include "llinstancetracker.h"
+#include "llexception.h"
#include <string>
#include <vector>
-#include <stdexcept>
/**
* LLSD Event API Plugin class. Because instances are managed by
@@ -67,9 +67,9 @@ public:
* string(s) passed to create() might come from an external source. This
* way the caller can catch LLLeap::Error and try to recover.
*/
- struct Error: public std::runtime_error
+ struct Error: public LLException
{
- Error(const std::string& what): std::runtime_error(what) {}
+ Error(const std::string& what): LLException(what) {}
};
virtual ~LLLeap();
diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h
index 9a662a0640..08744f90fb 100644
--- a/indra/llcommon/llmake.h
+++ b/indra/llcommon/llmake.h
@@ -12,12 +12,10 @@
*
* also relevant:
*
- * Template parameter deduction for constructors
- * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
- *
- * https://github.com/viboes/std-make
- *
- * but obviously we're not there yet.
+ * Template argument deduction for class templates
+ * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html
+ * was apparently adopted in June 2016? Unclear when compilers will
+ * portably support this, but there is hope.
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Copyright (c) 2015, Linden Research, Inc.
diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp
index 3a8eabac09..1e04044269 100644
--- a/indra/llcommon/llmemory.cpp
+++ b/indra/llcommon/llmemory.cpp
@@ -591,7 +591,7 @@ char* LLPrivateMemoryPool::LLMemoryBlock::allocate()
void LLPrivateMemoryPool::LLMemoryBlock::freeMem(void* addr)
{
//bit index
- U32 idx = ((U32)addr - (U32)mBuffer - mDummySize) / mSlotSize ;
+ uintptr_t idx = ((uintptr_t)addr - (uintptr_t)mBuffer - mDummySize) / mSlotSize ;
U32* bits = &mUsageBits ;
if(idx >= 32)
@@ -773,7 +773,7 @@ char* LLPrivateMemoryPool::LLMemoryChunk::allocate(U32 size)
void LLPrivateMemoryPool::LLMemoryChunk::freeMem(void* addr)
{
- U32 blk_idx = getPageIndex((U32)addr) ;
+ U32 blk_idx = getPageIndex((uintptr_t)addr) ;
LLMemoryBlock* blk = (LLMemoryBlock*)(mMetaBuffer + blk_idx * sizeof(LLMemoryBlock)) ;
blk = blk->mSelf ;
@@ -798,7 +798,7 @@ bool LLPrivateMemoryPool::LLMemoryChunk::empty()
bool LLPrivateMemoryPool::LLMemoryChunk::containsAddress(const char* addr) const
{
- return (U32)mBuffer <= (U32)addr && (U32)mBuffer + mBufferSize > (U32)addr ;
+ return (uintptr_t)mBuffer <= (uintptr_t)addr && (uintptr_t)mBuffer + mBufferSize > (uintptr_t)addr ;
}
//debug use
@@ -831,13 +831,13 @@ void LLPrivateMemoryPool::LLMemoryChunk::dump()
for(U32 i = 1 ; i < blk_list.size(); i++)
{
total_size += blk_list[i]->getBufferSize() ;
- if((U32)blk_list[i]->getBuffer() < (U32)blk_list[i-1]->getBuffer() + blk_list[i-1]->getBufferSize())
+ if((uintptr_t)blk_list[i]->getBuffer() < (uintptr_t)blk_list[i-1]->getBuffer() + blk_list[i-1]->getBufferSize())
{
LL_ERRS() << "buffer corrupted." << LL_ENDL ;
}
}
- llassert_always(total_size + mMinBlockSize >= mBufferSize - ((U32)mDataBuffer - (U32)mBuffer)) ;
+ llassert_always(total_size + mMinBlockSize >= mBufferSize - ((uintptr_t)mDataBuffer - (uintptr_t)mBuffer)) ;
U32 blk_num = (mBufferSize - (mDataBuffer - mBuffer)) / mMinBlockSize ;
for(U32 i = 0 ; i < blk_num ; )
@@ -860,7 +860,7 @@ void LLPrivateMemoryPool::LLMemoryChunk::dump()
#endif
#if 0
LL_INFOS() << "---------------------------" << LL_ENDL ;
- LL_INFOS() << "Chunk buffer: " << (U32)getBuffer() << " size: " << getBufferSize() << LL_ENDL ;
+ LL_INFOS() << "Chunk buffer: " << (uintptr_t)getBuffer() << " size: " << getBufferSize() << LL_ENDL ;
LL_INFOS() << "available blocks ... " << LL_ENDL ;
for(S32 i = 0 ; i < mBlockLevels ; i++)
@@ -868,7 +868,7 @@ void LLPrivateMemoryPool::LLMemoryChunk::dump()
LLMemoryBlock* blk = mAvailBlockList[i] ;
while(blk)
{
- LL_INFOS() << "blk buffer " << (U32)blk->getBuffer() << " size: " << blk->getBufferSize() << LL_ENDL ;
+ LL_INFOS() << "blk buffer " << (uintptr_t)blk->getBuffer() << " size: " << blk->getBufferSize() << LL_ENDL ;
blk = blk->mNext ;
}
}
@@ -879,7 +879,7 @@ void LLPrivateMemoryPool::LLMemoryChunk::dump()
LLMemoryBlock* blk = mFreeSpaceList[i] ;
while(blk)
{
- LL_INFOS() << "blk buffer " << (U32)blk->getBuffer() << " size: " << blk->getBufferSize() << LL_ENDL ;
+ LL_INFOS() << "blk buffer " << (uintptr_t)blk->getBuffer() << " size: " << blk->getBufferSize() << LL_ENDL ;
blk = blk->mNext ;
}
}
@@ -1155,9 +1155,9 @@ void LLPrivateMemoryPool::LLMemoryChunk::addToAvailBlockList(LLMemoryBlock* blk)
return ;
}
-U32 LLPrivateMemoryPool::LLMemoryChunk::getPageIndex(U32 addr)
+U32 LLPrivateMemoryPool::LLMemoryChunk::getPageIndex(uintptr_t addr)
{
- return (addr - (U32)mDataBuffer) / mMinBlockSize ;
+ return (addr - (uintptr_t)mDataBuffer) / mMinBlockSize ;
}
//for mAvailBlockList
@@ -1495,7 +1495,7 @@ void LLPrivateMemoryPool::removeChunk(LLMemoryChunk* chunk)
U16 LLPrivateMemoryPool::findHashKey(const char* addr)
{
- return (((U32)addr) / CHUNK_SIZE) % mHashFactor ;
+ return (((uintptr_t)addr) / CHUNK_SIZE) % mHashFactor ;
}
LLPrivateMemoryPool::LLMemoryChunk* LLPrivateMemoryPool::findChunk(const char* addr)
@@ -1720,7 +1720,7 @@ LLPrivateMemoryPoolManager::~LLPrivateMemoryPoolManager()
S32 k = 0 ;
for(mem_allocation_info_t::iterator iter = sMemAllocationTracker.begin() ; iter != sMemAllocationTracker.end() ; ++iter)
{
- LL_INFOS() << k++ << ", " << (U32)iter->first << " : " << iter->second << LL_ENDL ;
+ LL_INFOS() << k++ << ", " << (uintptr_t)iter->first << " : " << iter->second << LL_ENDL ;
}
sMemAllocationTracker.clear() ;
}
diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h
index 0fb257aab1..5a3c9bd762 100644
--- a/indra/llcommon/llmemory.h
+++ b/indra/llcommon/llmemory.h
@@ -110,11 +110,15 @@ template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address)
#if defined(LL_WINDOWS)
return _aligned_malloc(size, align);
#else
+ char* aligned = NULL;
void* mem = malloc( size + (align - 1) + sizeof(void*) );
- char* aligned = ((char*)mem) + sizeof(void*);
- aligned += align - ((uintptr_t)aligned & (align - 1));
+ if (mem)
+ {
+ aligned = ((char*)mem) + sizeof(void*);
+ aligned += align - ((uintptr_t)aligned & (align - 1));
- ((void**)aligned)[-1] = mem;
+ ((void**)aligned)[-1] = mem;
+ }
return aligned;
#endif
}
@@ -134,7 +138,6 @@ template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address)
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
-#if !LL_USE_TCMALLOC
inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16().
{
#if defined(LL_WINDOWS)
@@ -183,13 +186,6 @@ inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // r
#endif
}
-#else // USE_TCMALLOC
-// ll_aligned_foo_16 are not needed with tcmalloc
-#define ll_aligned_malloc_16 malloc
-#define ll_aligned_realloc_16(a,b,c) realloc(a,b)
-#define ll_aligned_free_16 free
-#endif // USE_TCMALLOC
-
inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32().
{
#if defined(LL_WINDOWS)
@@ -419,7 +415,7 @@ public:
{
bool operator()(const LLMemoryBlock* const& lhs, const LLMemoryBlock* const& rhs)
{
- return (U32)lhs->getBuffer() < (U32)rhs->getBuffer();
+ return (uintptr_t)lhs->getBuffer() < (uintptr_t)rhs->getBuffer();
}
};
};
@@ -450,7 +446,7 @@ public:
void dump() ;
private:
- U32 getPageIndex(U32 addr) ;
+ U32 getPageIndex(uintptr_t addr) ;
U32 getBlockLevel(U32 size) ;
U16 getPageLevel(U32 size) ;
LLMemoryBlock* addBlock(U32 blk_idx) ;
diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp
index 1fc821d9a9..16fc365da1 100644
--- a/indra/llcommon/llmetricperformancetester.cpp
+++ b/indra/llcommon/llmetricperformancetester.cpp
@@ -40,7 +40,7 @@
LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ;
/*static*/
-void LLMetricPerformanceTesterBasic::cleanClass()
+void LLMetricPerformanceTesterBasic::cleanupClass()
{
for (name_tester_map_t::iterator iter = sTesterMap.begin() ; iter != sTesterMap.end() ; ++iter)
{
diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h
index 1a18cdf36f..e6b46be1cf 100644
--- a/indra/llcommon/llmetricperformancetester.h
+++ b/indra/llcommon/llmetricperformancetester.h
@@ -156,7 +156,7 @@ public:
/**
* @brief Delete all testers and reset the tester map
*/
- static void cleanClass() ;
+ static void cleanupClass() ;
private:
// Add a tester to the map. Returns false if adding fails.
diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h
new file mode 100644
index 0000000000..0421ce966a
--- /dev/null
+++ b/indra/llcommon/llpounceable.h
@@ -0,0 +1,216 @@
+/**
+ * @file llpounceable.h
+ * @author Nat Goodspeed
+ * @date 2015-05-22
+ * @brief LLPounceable is tangentially related to a future: it's a holder for
+ * a value that may or may not exist yet. Unlike a future, though,
+ * LLPounceable freely allows reading the held value. (If the held
+ * type T does not have a distinguished "empty" value, consider using
+ * LLPounceable<boost::optional<T>>.)
+ *
+ * LLPounceable::callWhenReady() is this template's claim to fame. It
+ * allows its caller to "pounce" on the held value as soon as it
+ * becomes non-empty. Call callWhenReady() with any C++ callable
+ * accepting T. If the held value is already non-empty, callWhenReady()
+ * will immediately call the callable with the held value. If the held
+ * value is empty, though, callWhenReady() will enqueue the callable
+ * for later. As soon as LLPounceable is assigned a non-empty held
+ * value, it will flush the queue of deferred callables.
+ *
+ * Consider a global LLMessageSystem* gMessageSystem. Message system
+ * initialization happens at a very specific point during viewer
+ * initialization. Other subsystems want to register callbacks on the
+ * LLMessageSystem instance as soon as it's initialized, but their own
+ * initialization may precede that. If we define gMessageSystem to be
+ * an LLPounceable<LLMessageSystem*>, a subsystem can use
+ * callWhenReady() to either register immediately (if gMessageSystem
+ * is already up and runnning) or register as soon as gMessageSystem
+ * is set with a new, initialized instance.
+ *
+ * $LicenseInfo:firstyear=2015&license=viewerlgpl$
+ * Copyright (c) 2015, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLPOUNCEABLE_H)
+#define LL_LLPOUNCEABLE_H
+
+#include "llsingleton.h"
+#include <boost/noncopyable.hpp>
+#include <boost/call_traits.hpp>
+#include <boost/type_traits/remove_pointer.hpp>
+#include <boost/utility/value_init.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/signals2/signal.hpp>
+
+// Forward declare the user template, since we want to be able to point to it
+// in some of its implementation classes.
+template <typename T, class TAG>
+class LLPounceable;
+
+template <typename T, typename TAG>
+struct LLPounceableTraits
+{
+ // Our "queue" is a signal object with correct signature.
+ typedef boost::signals2::signal<void (typename boost::call_traits<T>::param_type)> signal_t;
+ // Call callWhenReady() with any callable accepting T.
+ typedef typename signal_t::slot_type func_t;
+ // owner pointer type
+ typedef LLPounceable<T, TAG>* owner_ptr;
+};
+
+// Tag types distinguish the two different implementations of LLPounceable's
+// queue.
+struct LLPounceableQueue {};
+struct LLPounceableStatic {};
+
+// generic LLPounceableQueueImpl deliberately omitted: only the above tags are
+// legal
+template <typename T, class TAG>
+class LLPounceableQueueImpl;
+
+// The implementation selected by LLPounceableStatic uses an LLSingleton
+// because we can't count on a data member queue being initialized at the time
+// we start getting callWhenReady() calls. This is that LLSingleton.
+template <typename T>
+class LLPounceableQueueSingleton:
+ public LLSingleton<LLPounceableQueueSingleton<T> >
+{
+ LLSINGLETON_EMPTY_CTOR(LLPounceableQueueSingleton);
+
+ typedef LLPounceableTraits<T, LLPounceableStatic> traits;
+ typedef typename traits::owner_ptr owner_ptr;
+ typedef typename traits::signal_t signal_t;
+
+ // For a given held type T, every LLPounceable<T, LLPounceableStatic>
+ // instance will call on the SAME LLPounceableQueueSingleton instance --
+ // given how class statics work. We must keep a separate queue for each
+ // LLPounceable instance. Use a hash map for that.
+ typedef boost::unordered_map<owner_ptr, signal_t> map_t;
+
+public:
+ // Disambiguate queues belonging to different LLPounceables.
+ signal_t& get(owner_ptr owner)
+ {
+ // operator[] has find-or-create semantics -- just what we want!
+ return mMap[owner];
+ }
+
+private:
+ map_t mMap;
+};
+
+// LLPounceableQueueImpl that uses the above LLSingleton
+template <typename T>
+class LLPounceableQueueImpl<T, LLPounceableStatic>
+{
+public:
+ typedef LLPounceableTraits<T, LLPounceableStatic> traits;
+ typedef typename traits::owner_ptr owner_ptr;
+ typedef typename traits::signal_t signal_t;
+
+ signal_t& get(owner_ptr owner) const
+ {
+ // this Impl contains nothing; it delegates to the Singleton
+ return LLPounceableQueueSingleton<T>::instance().get(owner);
+ }
+};
+
+// The implementation selected by LLPounceableQueue directly contains the
+// queue of interest, suitable for an LLPounceable we can trust to be fully
+// initialized when it starts getting callWhenReady() calls.
+template <typename T>
+class LLPounceableQueueImpl<T, LLPounceableQueue>
+{
+public:
+ typedef LLPounceableTraits<T, LLPounceableQueue> traits;
+ typedef typename traits::owner_ptr owner_ptr;
+ typedef typename traits::signal_t signal_t;
+
+ signal_t& get(owner_ptr)
+ {
+ return mQueue;
+ }
+
+private:
+ signal_t mQueue;
+};
+
+// LLPounceable<T> is for an LLPounceable instance on the heap or the stack.
+// LLPounceable<T, LLPounceableStatic> is for a static LLPounceable instance.
+template <typename T, class TAG=LLPounceableQueue>
+class LLPounceable: public boost::noncopyable
+{
+private:
+ typedef LLPounceableTraits<T, TAG> traits;
+ typedef typename traits::owner_ptr owner_ptr;
+ typedef typename traits::signal_t signal_t;
+
+public:
+ typedef typename traits::func_t func_t;
+
+ // By default, both the initial value and the distinguished empty value
+ // are a default-constructed T instance. However you can explicitly
+ // specify each.
+ LLPounceable(typename boost::call_traits<T>::value_type init =boost::value_initialized<T>(),
+ typename boost::call_traits<T>::param_type empty=boost::value_initialized<T>()):
+ mHeld(init),
+ mEmpty(empty)
+ {}
+
+ // make read access to mHeld as cheap and transparent as possible
+ operator T () const { return mHeld; }
+ typename boost::remove_pointer<T>::type operator*() const { return *mHeld; }
+ typename boost::call_traits<T>::value_type operator->() const { return mHeld; }
+ // uncomment 'explicit' as soon as we allow C++11 compilation
+ /*explicit*/ operator bool() const { return bool(mHeld); }
+ bool operator!() const { return ! mHeld; }
+
+ // support both assignment (dumb ptr idiom) and reset() (smart ptr)
+ void operator=(typename boost::call_traits<T>::param_type value)
+ {
+ reset(value);
+ }
+
+ void reset(typename boost::call_traits<T>::param_type value)
+ {
+ mHeld = value;
+ // If this new value is non-empty, flush anything pending in the queue.
+ if (mHeld != mEmpty)
+ {
+ signal_t& signal(get_signal());
+ signal(mHeld);
+ signal.disconnect_all_slots();
+ }
+ }
+
+ // our claim to fame
+ void callWhenReady(const func_t& func)
+ {
+ if (mHeld != mEmpty)
+ {
+ // If the held value is already non-empty, immediately call func()
+ func(mHeld);
+ }
+ else
+ {
+ // Held value still empty, queue func() for later. By default,
+ // connect() enqueues slots in FIFO order.
+ get_signal().connect(func);
+ }
+ }
+
+private:
+ signal_t& get_signal() { return mQueue.get(this); }
+
+ // Store both the current and the empty value.
+ // MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of
+ // testing for "empty." For some types we want operator!(); for others we
+ // want to compare to a distinguished value.
+ typename boost::call_traits<T>::value_type mHeld, mEmpty;
+ // This might either contain the queue (LLPounceableQueue) or delegate to
+ // an LLSingleton (LLPounceableStatic).
+ LLPounceableQueueImpl<T, TAG> mQueue;
+};
+
+#endif /* ! defined(LL_LLPOUNCEABLE_H) */
diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h
index 2c4bcc91f6..3698d9db44 100644
--- a/indra/llcommon/llpreprocessor.h
+++ b/indra/llcommon/llpreprocessor.h
@@ -138,6 +138,12 @@
#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 <map> 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 : 4800 ) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
#pragma warning( disable : 4996 ) // warning: deprecated
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 44f56daf2d..8c321d06b9 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -34,6 +34,7 @@
#include "llapr.h"
#include "apr_signal.h"
#include "llevents.h"
+#include "llexception.h"
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
@@ -472,9 +473,9 @@ private:
*****************************************************************************/
/// Need an exception to avoid constructing an invalid LLProcess object, but
/// internal use only
-struct LLProcessError: public std::runtime_error
+struct LLProcessError: public LLException
{
- LLProcessError(const std::string& msg): std::runtime_error(msg) {}
+ LLProcessError(const std::string& msg): LLException(msg) {}
};
LLProcessPtr LLProcess::create(const LLSDOrParams& params)
@@ -530,8 +531,8 @@ LLProcess::LLProcess(const LLSDOrParams& params):
if (! params.validateBlock(true))
{
- throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
- << LLSDNotationStreamer(params)));
+ LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
+ << LLSDNotationStreamer(params))));
}
mPostend = params.postend;
@@ -596,10 +597,10 @@ LLProcess::LLProcess(const LLSDOrParams& params):
}
else
{
- throw LLProcessError(STRINGIZE("For " << params.executable()
- << ": unsupported FileParam for " << which
- << ": type='" << fparam.type()
- << "', name='" << fparam.name() << "'"));
+ 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.
@@ -678,7 +679,7 @@ LLProcess::LLProcess(const LLSDOrParams& params):
if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
gAPRPoolp)))
{
- throw LLProcessError(STRINGIZE(params << " failed"));
+ LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
}
// arrange to call status_callback()
@@ -1063,7 +1064,7 @@ PIPETYPE& LLProcess::getPipe(FILESLOT slot)
PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
if (! wp)
{
- throw NoPipe(error);
+ LLTHROW(NoPipe(error));
}
return *wp;
}
diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h
index 43ccadc412..bfac4567a5 100644
--- a/indra/llcommon/llprocess.h
+++ b/indra/llcommon/llprocess.h
@@ -30,13 +30,13 @@
#include "llinitparam.h"
#include "llsdparam.h"
#include "llwin32headerslean.h"
+#include "llexception.h"
#include "apr_thread_proc.h"
#include <boost/shared_ptr.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/optional.hpp>
#include <boost/noncopyable.hpp>
#include <iosfwd> // std::ostream
-#include <stdexcept>
#if LL_WINDOWS
#include "llwin32headerslean.h" // for HANDLE
@@ -479,9 +479,9 @@ public:
/// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
/// create a pipe at the corresponding FILESLOT.
- struct NoPipe: public std::runtime_error
+ struct NoPipe: public LLException
{
- NoPipe(const std::string& what): std::runtime_error(what) {}
+ NoPipe(const std::string& what): LLException(what) {}
};
/**
diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp
index e3e1d0c391..65b4507e2d 100644
--- a/indra/llcommon/llprocessor.cpp
+++ b/indra/llcommon/llprocessor.cpp
@@ -398,7 +398,7 @@ static F64 calculate_cpu_frequency(U32 measure_msecs)
HANDLE hThread = GetCurrentThread();
unsigned long dwCurPriorityClass = GetPriorityClass(hProcess);
int iCurThreadPriority = GetThreadPriority(hThread);
- unsigned long dwProcessMask, dwSystemMask, dwNewMask = 1;
+ DWORD_PTR dwProcessMask, dwSystemMask, dwNewMask = 1;
GetProcessAffinityMask(hProcess, &dwProcessMask, &dwSystemMask);
SetPriorityClass(hProcess, REALTIME_PRIORITY_CLASS);
diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h
index 29950c108d..750fe9fdc8 100644
--- a/indra/llcommon/llregistry.h
+++ b/indra/llcommon/llregistry.h
@@ -247,7 +247,10 @@ class LLRegistrySingleton
: public LLRegistry<KEY, VALUE, COMPARATOR>,
public LLSingleton<DERIVED_TYPE>
{
- friend class LLSingleton<DERIVED_TYPE>;
+ // 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<KEY, VALUE, COMPARATOR> registry_t;
typedef const KEY& ref_const_key_t;
@@ -269,7 +272,7 @@ public:
~ScopedRegistrar()
{
- if (!singleton_t::destroyed())
+ if (singleton_t::instanceExists())
{
popScope();
}
diff --git a/indra/llcommon/llsdparam.h b/indra/llcommon/llsdparam.h
index 09f1bdf1e3..93910b70ae 100644
--- a/indra/llcommon/llsdparam.h
+++ b/indra/llcommon/llsdparam.h
@@ -66,6 +66,7 @@ public:
}
/*virtual*/ std::string getCurrentElementName();
+ /*virtual*/ std::string getCurrentFileName(){ return LLStringUtil::null; }
private:
void writeSDImpl(LLSD& sd,
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index d49ff0feb5..81ba8631c6 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -1200,6 +1200,7 @@ bool LLSDBinaryParser::parseString(
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<char> buf;
if(size)
{
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index 9b49e52377..9025e53bb2 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -25,7 +25,445 @@
*/
#include "linden_common.h"
-
#include "llsingleton.h"
+#include "llerror.h"
+#include "llerrorcontrol.h" // LLError::is_available()
+#include "lldependencies.h"
+#include "llcoro_get_id.h"
+#include <boost/foreach.hpp>
+#include <boost/unordered_map.hpp>
+#include <algorithm>
+#include <iostream> // std::cerr in dire emergency
+#include <sstream>
+#include <stdexcept>
+
+namespace {
+void log(LLError::ELevel level,
+ const char* p1, const char* p2, const char* p3, const char* p4);
+
+void logdebugs(const char* p1="", const char* p2="", const char* p3="", const char* p4="");
+
+bool oktolog();
+} // anonymous namespace
+
+// Our master list of all LLSingletons is itself an LLSingleton. We used to
+// store it in a function-local static, but that could get destroyed before
+// the last of the LLSingletons -- and ~LLSingletonBase() definitely wants to
+// remove itself from the master list. Since the whole point of this master
+// list is to help track inter-LLSingleton dependencies, and since we have
+// this implicit dependency from every LLSingleton to the master list, make it
+// an LLSingleton.
+class LLSingletonBase::MasterList:
+ public LLSingleton<LLSingletonBase::MasterList>
+{
+ LLSINGLETON_EMPTY_CTOR(MasterList);
+
+public:
+ // No need to make this private with accessors; nobody outside this source
+ // file can see it.
+
+ // This is the master list of all instantiated LLSingletons (save the
+ // MasterList itself) in arbitrary order. You MUST call dep_sort() before
+ // traversing this list.
+ LLSingletonBase::list_t mMaster;
+
+ // We need to maintain a stack of LLSingletons currently being
+ // initialized, either in the constructor or in initSingleton(). However,
+ // managing that as a stack depends on having a DISTINCT 'initializing'
+ // stack for every C++ stack in the process! And we have a distinct C++
+ // stack for every running coroutine. It would be interesting and cool to
+ // implement a generic coroutine-local-storage mechanism and use that
+ // here. The trouble is that LLCoros is itself an LLSingleton, so
+ // depending on LLCoros functionality could dig us into infinite
+ // recursion. (Moreover, when we reimplement LLCoros on top of
+ // Boost.Fiber, that library already provides fiber_specific_ptr -- so
+ // it's not worth a great deal of time and energy implementing a generic
+ // equivalent on top of boost::dcoroutine, which is on its way out.)
+ // Instead, use a map of llcoro::id to select the appropriate
+ // coro-specific 'initializing' stack. llcoro::get_id() is carefully
+ // implemented to avoid requiring LLCoros.
+ typedef boost::unordered_map<llcoro::id, LLSingletonBase::list_t> InitializingMap;
+ InitializingMap mInitializing;
+
+ // non-static method, cf. LLSingletonBase::get_initializing()
+ list_t& get_initializing_()
+ {
+ // map::operator[] has find-or-create semantics, exactly what we need
+ // here. It returns a reference to the selected mapped_type instance.
+ return mInitializing[llcoro::get_id()];
+ }
+
+ void cleanup_initializing_()
+ {
+ InitializingMap::iterator found = mInitializing.find(llcoro::get_id());
+ if (found != mInitializing.end())
+ {
+ mInitializing.erase(found);
+ }
+ }
+};
+
+//static
+LLSingletonBase::list_t& LLSingletonBase::get_master()
+{
+ return LLSingletonBase::MasterList::instance().mMaster;
+}
+
+void LLSingletonBase::add_master()
+{
+ // As each new LLSingleton is constructed, add to the master list.
+ get_master().push_back(this);
+}
+
+void LLSingletonBase::remove_master()
+{
+ // When an LLSingleton is destroyed, remove from master list.
+ // add_master() used to capture the iterator to the newly-added list item
+ // so we could directly erase() it from the master list. Unfortunately
+ // that runs afoul of destruction-dependency order problems. So search the
+ // master list, and remove this item IF FOUND. We have few enough
+ // LLSingletons, and they are so rarely destroyed (once per run), that the
+ // cost of a linear search should not be an issue.
+ get_master().remove(this);
+}
+
+//static
+LLSingletonBase::list_t& LLSingletonBase::get_initializing()
+{
+ return LLSingletonBase::MasterList::instance().get_initializing_();
+}
+
+//static
+LLSingletonBase::list_t& LLSingletonBase::get_initializing_from(MasterList* master)
+{
+ return master->get_initializing_();
+}
+
+LLSingletonBase::~LLSingletonBase() {}
+
+void LLSingletonBase::push_initializing(const char* name)
+{
+ // log BEFORE pushing so logging singletons don't cry circularity
+ log_initializing("Pushing", name);
+ get_initializing().push_back(this);
+}
+
+void LLSingletonBase::pop_initializing()
+{
+ list_t& list(get_initializing());
+
+ if (list.empty())
+ {
+ logerrs("Underflow in stack of currently-initializing LLSingletons at ",
+ demangle(typeid(*this).name()).c_str(), "::getInstance()");
+ }
+
+ // Now we know list.back() exists: capture it
+ LLSingletonBase* back(list.back());
+ // and pop it
+ list.pop_back();
+
+ // The viewer launches an open-ended number of coroutines. While we don't
+ // expect most of them to initialize LLSingleton instances, our present
+ // get_initializing() logic could lead to an open-ended number of map
+ // entries. So every time we pop the stack back to empty, delete the entry
+ // entirely.
+ if (list.empty())
+ {
+ MasterList::instance().cleanup_initializing_();
+ }
+
+ // Now validate the newly-popped LLSingleton.
+ if (back != this)
+ {
+ logerrs("Push/pop mismatch in stack of currently-initializing LLSingletons: ",
+ demangle(typeid(*this).name()).c_str(), "::getInstance() trying to pop ",
+ demangle(typeid(*back).name()).c_str());
+ }
+
+ // log AFTER popping so logging singletons don't cry circularity
+ log_initializing("Popping", typeid(*back).name());
+}
+
+//static
+void LLSingletonBase::log_initializing(const char* verb, const char* name)
+{
+ if (oktolog())
+ {
+ LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';';
+ list_t& list(get_initializing());
+ for (list_t::const_reverse_iterator ri(list.rbegin()), rend(list.rend());
+ ri != rend; ++ri)
+ {
+ LLSingletonBase* sb(*ri);
+ LL_CONT << ' ' << demangle(typeid(*sb).name());
+ }
+ LL_ENDL;
+ }
+}
+
+void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState)
+{
+ // Did this getInstance() call come from another LLSingleton, or from
+ // vanilla application code? Note that although this is a nontrivial
+ // method, the vast majority of its calls arrive here with initializing
+ // empty().
+ if (! initializing.empty())
+ {
+ // getInstance() is being called by some other LLSingleton. But -- is
+ // this a circularity? That is, does 'this' already appear in the
+ // initializing stack?
+ // For what it's worth, normally 'initializing' should contain very
+ // few elements.
+ list_t::const_iterator found =
+ std::find(initializing.begin(), initializing.end(), this);
+ if (found != initializing.end())
+ {
+ // Report the circularity. Requiring the coder to dig through the
+ // logic to diagnose exactly how we got here is less than helpful.
+ std::ostringstream out;
+ for ( ; found != initializing.end(); ++found)
+ {
+ // 'found' is an iterator; *found is an LLSingletonBase*; **found
+ // is the actual LLSingletonBase instance.
+ LLSingletonBase* foundp(*found);
+ out << demangle(typeid(*foundp).name()) << " -> ";
+ }
+ // We promise to capture dependencies from both the constructor
+ // and the initSingleton() method, so an LLSingleton's instance
+ // pointer is on the initializing list during both. Now that we've
+ // detected circularity, though, we must distinguish the two. If
+ // the recursive call is from the constructor, we CAN'T honor it:
+ // otherwise we'd be returning a pointer to a partially-
+ // constructed object! But from initSingleton() is okay: that
+ // method exists specifically to support circularity.
+ // Decide which log helper to call based on initState. They have
+ // identical signatures.
+ ((initState == CONSTRUCTING)? logerrs : logwarns)
+ ("LLSingleton circularity: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ else
+ {
+ // Here 'this' is NOT already in the 'initializing' stack. Great!
+ // Record the dependency.
+ // initializing.back() is the LLSingletonBase* currently being
+ // initialized. Store 'this' in its mDepends set.
+ LLSingletonBase* current(initializing.back());
+ if (current->mDepends.insert(this).second)
+ {
+ // only log the FIRST time we hit this dependency!
+ logdebugs(demangle(typeid(*current).name()).c_str(),
+ " depends on ", demangle(typeid(*this).name()).c_str());
+ }
+ }
+ }
+}
+
+//static
+LLSingletonBase::vec_t LLSingletonBase::dep_sort()
+{
+ // While it would theoretically be possible to maintain a static
+ // SingletonDeps through the life of the program, dynamically adding and
+ // removing LLSingletons as they are created and destroyed, in practice
+ // it's less messy to construct it on demand. The overhead of doing so
+ // should happen basically twice: once for cleanupAll(), once for
+ // deleteAll().
+ typedef LLDependencies<LLSingletonBase*> SingletonDeps;
+ SingletonDeps sdeps;
+ list_t& master(get_master());
+ BOOST_FOREACH(LLSingletonBase* sp, master)
+ {
+ // Build the SingletonDeps structure by adding, for each
+ // LLSingletonBase* sp in the master list, sp itself. It has no
+ // associated value type in our SingletonDeps, hence the 0. We don't
+ // record the LLSingletons it must follow; rather, we record the ones
+ // it must precede. Copy its mDepends to a KeyList to express that.
+ sdeps.add(sp, 0,
+ SingletonDeps::KeyList(),
+ SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end()));
+ }
+ vec_t ret;
+ ret.reserve(master.size());
+ // We should be able to effect this with a transform_iterator that
+ // extracts just the first (key) element from each sorted_iterator, then
+ // uses vec_t's range constructor... but frankly this is more
+ // straightforward, as long as we remember the above reserve() call!
+ BOOST_FOREACH(SingletonDeps::sorted_iterator::value_type pair, sdeps.sort())
+ {
+ ret.push_back(pair.first);
+ }
+ // The master list is not itself pushed onto the master list. Add it as
+ // the very last entry -- it is the LLSingleton on which ALL others
+ // depend! -- so our caller will process it.
+ ret.push_back(MasterList::getInstance());
+ return ret;
+}
+
+//static
+void LLSingletonBase::cleanupAll()
+{
+ // It's essential to traverse these in dependency order.
+ BOOST_FOREACH(LLSingletonBase* sp, dep_sort())
+ {
+ // Call cleanupSingleton() only if we haven't already done so for this
+ // instance.
+ if (! sp->mCleaned)
+ {
+ sp->mCleaned = true;
+
+ logdebugs("calling ",
+ demangle(typeid(*sp).name()).c_str(), "::cleanupSingleton()");
+ try
+ {
+ sp->cleanupSingleton();
+ }
+ catch (const std::exception& e)
+ {
+ logwarns("Exception in ", demangle(typeid(*sp).name()).c_str(),
+ "::cleanupSingleton(): ", e.what());
+ }
+ catch (...)
+ {
+ logwarns("Unknown exception in ", demangle(typeid(*sp).name()).c_str(),
+ "::cleanupSingleton()");
+ }
+ }
+ }
+}
+
+//static
+void LLSingletonBase::deleteAll()
+{
+ // It's essential to traverse these in dependency order.
+ BOOST_FOREACH(LLSingletonBase* sp, dep_sort())
+ {
+ // Capture the class name first: in case of exception, don't count on
+ // being able to extract it later.
+ const std::string name = demangle(typeid(*sp).name());
+ try
+ {
+ // Call static method through instance function pointer.
+ if (! sp->mDeleteSingleton)
+ {
+ // This Should Not Happen... but carry on.
+ logwarns(name.c_str(), "::mDeleteSingleton not initialized!");
+ }
+ else
+ {
+ // properly initialized: call it.
+ logdebugs("calling ", name.c_str(), "::deleteSingleton()");
+ // From this point on, DO NOT DEREFERENCE sp!
+ sp->mDeleteSingleton();
+ }
+ }
+ catch (const std::exception& e)
+ {
+ logwarns("Exception in ", name.c_str(), "::deleteSingleton(): ", e.what());
+ }
+ catch (...)
+ {
+ logwarns("Unknown exception in ", name.c_str(), "::deleteSingleton()");
+ }
+ }
+}
+
+/*------------------------ Final cleanup management ------------------------*/
+class LLSingletonBase::MasterRefcount
+{
+public:
+ // store a POD int so it will be statically initialized to 0
+ int refcount;
+};
+static LLSingletonBase::MasterRefcount sMasterRefcount;
+
+LLSingletonBase::ref_ptr_t LLSingletonBase::get_master_refcount()
+{
+ // Calling this method constructs a new ref_ptr_t, which implicitly calls
+ // intrusive_ptr_add_ref(MasterRefcount*).
+ return &sMasterRefcount;
+}
+
+void intrusive_ptr_add_ref(LLSingletonBase::MasterRefcount* mrc)
+{
+ // Count outstanding SingletonLifetimeManager instances.
+ ++mrc->refcount;
+}
+
+void intrusive_ptr_release(LLSingletonBase::MasterRefcount* mrc)
+{
+ // Notice when each SingletonLifetimeManager instance is destroyed.
+ if (! --mrc->refcount)
+ {
+ // The last instance was destroyed. Time to kill any remaining
+ // LLSingletons -- but in dependency order.
+ LLSingletonBase::deleteAll();
+ }
+}
+
+/*---------------------------- Logging helpers -----------------------------*/
+namespace {
+bool oktolog()
+{
+ // See comments in log() below.
+ return sMasterRefcount.refcount && LLError::is_available();
+}
+
+void log(LLError::ELevel level,
+ const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ // Check whether we're in the implicit final LLSingletonBase::deleteAll()
+ // call. We've carefully arranged for deleteAll() to be called when the
+ // last SingletonLifetimeManager instance is destroyed -- in other words,
+ // when the last translation unit containing an LLSingleton instance
+ // cleans up static data. That could happen after std::cerr is destroyed!
+ // The is_available() test below ensures that we'll stop logging once
+ // LLError has been cleaned up. If we had a similar portable test for
+ // std::cerr, this would be a good place to use it. As we do not, just
+ // don't log anything during implicit final deleteAll(). Detect that by
+ // the master refcount having gone to zero.
+ if (sMasterRefcount.refcount == 0)
+ return;
+
+ // Check LLError::is_available() because some of LLError's infrastructure
+ // is itself an LLSingleton. If that LLSingleton has not yet been
+ // initialized, trying to log will engage LLSingleton machinery... and
+ // around and around we go.
+ if (LLError::is_available())
+ {
+ LL_VLOGS(level, "LLSingleton") << p1 << p2 << p3 << p4 << LL_ENDL;
+ }
+ else
+ {
+ // Caller may be a test program, or something else whose stderr is
+ // visible to the user.
+ std::cerr << p1 << p2 << p3 << p4 << std::endl;
+ }
+}
+
+void logdebugs(const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ log(LLError::LEVEL_DEBUG, p1, p2, p3, p4);
+}
+} // anonymous namespace
+
+//static
+void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ log(LLError::LEVEL_WARN, p1, p2, p3, p4);
+}
+
+//static
+void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4)
+{
+ log(LLError::LEVEL_ERROR, p1, p2, p3, p4);
+ // The other important side effect of LL_ERRS() is
+ // https://www.youtube.com/watch?v=OMG7paGJqhQ (emphasis on OMG)
+ LLError::crashAndLoop(std::string());
+}
+std::string LLSingletonBase::demangle(const char* mangled)
+{
+ return LLError::Log::demangle(mangled);
+}
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 6e6291a165..1b915dfd6e 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -25,188 +25,495 @@
#ifndef LLSINGLETON_H
#define LLSINGLETON_H
-#include "llerror.h" // *TODO: eliminate this
-
-#include <typeinfo>
#include <boost/noncopyable.hpp>
+#include <boost/unordered_set.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <list>
+#include <vector>
+#include <typeinfo>
+
+class LLSingletonBase: private boost::noncopyable
+{
+public:
+ class MasterList;
+ class MasterRefcount;
+ typedef boost::intrusive_ptr<MasterRefcount> ref_ptr_t;
+
+private:
+ // All existing LLSingleton instances are tracked in this master list.
+ typedef std::list<LLSingletonBase*> list_t;
+ static list_t& get_master();
+ // This, on the other hand, is a stack whose top indicates the LLSingleton
+ // currently being initialized.
+ static list_t& get_initializing();
+ static list_t& get_initializing_from(MasterList*);
+ // Produce a vector<LLSingletonBase*> of master list, in dependency order.
+ typedef std::vector<LLSingletonBase*> vec_t;
+ static vec_t dep_sort();
+
+ bool mCleaned; // cleanupSingleton() has been called
+ // we directly depend on these other LLSingletons
+ typedef boost::unordered_set<LLSingletonBase*> set_t;
+ set_t mDepends;
+
+protected:
+ typedef enum e_init_state
+ {
+ UNINITIALIZED = 0, // must be default-initialized state
+ CONSTRUCTING,
+ INITIALIZING,
+ INITIALIZED,
+ DELETED
+ } EInitState;
+
+ // Define tag<T> to pass to our template constructor. You can't explicitly
+ // invoke a template constructor with ordinary template syntax:
+ // http://stackoverflow.com/a/3960925/5533635
+ template <typename T>
+ struct tag
+ {
+ typedef T type;
+ };
+
+ // Base-class constructor should only be invoked by the DERIVED_TYPE
+ // constructor, which passes tag<DERIVED_TYPE> for various purposes.
+ template <typename DERIVED_TYPE>
+ LLSingletonBase(tag<DERIVED_TYPE>);
+ virtual ~LLSingletonBase();
+
+ // Every new LLSingleton should be added to/removed from the master list
+ void add_master();
+ void remove_master();
+ // with a little help from our friends.
+ template <class T> friend struct LLSingleton_manage_master;
+
+ // Maintain a stack of the LLSingleton subclass instance currently being
+ // initialized. We use this to notice direct dependencies: we want to know
+ // if A requires B. We deduce a dependency if while initializing A,
+ // control reaches B::getInstance().
+ // We want &A to be at the top of that stack during both A::A() and
+ // A::initSingleton(), since a call to B::getInstance() might occur during
+ // either.
+ // Unfortunately the desired timespan does not correspond neatly with a
+ // single C++ scope, else we'd use RAII to track it. But we do know that
+ // LLSingletonBase's constructor definitely runs just before
+ // LLSingleton's, which runs just before the specific subclass's.
+ void push_initializing(const char*);
+ // LLSingleton is, and must remain, the only caller to initSingleton().
+ // That being the case, we control exactly when it happens -- and we can
+ // pop the stack immediately thereafter.
+ void pop_initializing();
+private:
+ // logging
+ static void log_initializing(const char* verb, const char* name);
+protected:
+ // If a given call to B::getInstance() happens during either A::A() or
+ // A::initSingleton(), record that A directly depends on B.
+ void capture_dependency(list_t& initializing, EInitState);
+
+ // delegate LL_ERRS() logging to llsingleton.cpp
+ static void logerrs(const char* p1, const char* p2="",
+ const char* p3="", const char* p4="");
+ // delegate LL_WARNS() logging to llsingleton.cpp
+ static void logwarns(const char* p1, const char* p2="",
+ const char* p3="", const char* p4="");
+ static std::string demangle(const char* mangled);
+
+ // obtain canonical ref_ptr_t
+ static ref_ptr_t get_master_refcount();
+
+ // Default methods in case subclass doesn't declare them.
+ virtual void initSingleton() {}
+ virtual void cleanupSingleton() {}
+
+ // deleteSingleton() isn't -- and shouldn't be -- a virtual method. It's a
+ // class static. However, given only Foo*, deleteAll() does need to be
+ // able to reach Foo::deleteSingleton(). Make LLSingleton (which declares
+ // deleteSingleton()) store a pointer here. Since we know it's a static
+ // class method, a classic-C function pointer will do.
+ void (*mDeleteSingleton)();
+
+public:
+ /**
+ * Call this to call the cleanupSingleton() method for every LLSingleton
+ * constructed since the start of the last cleanupAll() call. (Any
+ * LLSingleton constructed DURING a cleanupAll() call won't be cleaned up
+ * until the next cleanupAll() call.) cleanupSingleton() neither deletes
+ * nor destroys its LLSingleton; therefore it's safe to include logic that
+ * might take significant realtime or even throw an exception.
+ *
+ * The most important property of cleanupAll() is that cleanupSingleton()
+ * methods are called in dependency order, leaf classes last. Thus, given
+ * two LLSingleton subclasses A and B, if A's dependency on B is properly
+ * expressed as a B::getInstance() or B::instance() call during either
+ * A::A() or A::initSingleton(), B will be cleaned up after A.
+ *
+ * If a cleanupSingleton() method throws an exception, the exception is
+ * logged, but cleanupAll() attempts to continue calling the rest of the
+ * cleanupSingleton() methods.
+ */
+ static void cleanupAll();
+ /**
+ * Call this to call the deleteSingleton() method for every LLSingleton
+ * constructed since the start of the last deleteAll() call. (Any
+ * LLSingleton constructed DURING a deleteAll() call won't be cleaned up
+ * until the next deleteAll() call.) deleteSingleton() deletes and
+ * destroys its LLSingleton. Any cleanup logic that might take significant
+ * realtime -- or throw an exception -- must not be placed in your
+ * LLSingleton's destructor, but rather in its cleanupSingleton() method.
+ *
+ * The most important property of deleteAll() is that deleteSingleton()
+ * methods are called in dependency order, leaf classes last. Thus, given
+ * two LLSingleton subclasses A and B, if A's dependency on B is properly
+ * expressed as a B::getInstance() or B::instance() call during either
+ * A::A() or A::initSingleton(), B will be cleaned up after A.
+ *
+ * If a deleteSingleton() method throws an exception, the exception is
+ * logged, but deleteAll() attempts to continue calling the rest of the
+ * deleteSingleton() methods.
+ */
+ static void deleteAll();
+};
+
+// support ref_ptr_t
+void intrusive_ptr_add_ref(LLSingletonBase::MasterRefcount*);
+void intrusive_ptr_release(LLSingletonBase::MasterRefcount*);
-// LLSingleton implements the getInstance() method part of the Singleton
-// pattern. It can't make the derived class constructors protected, though, so
-// you have to do that yourself.
-//
-// There are two ways to use LLSingleton. The first way is to inherit from it
-// while using the typename that you'd like to be static as the template
-// parameter, like so:
-//
-// class Foo: public LLSingleton<Foo>{};
-//
-// Foo& instance = Foo::instance();
-//
-// The second way is to use the singleton class directly, without inheritance:
-//
-// typedef LLSingleton<Foo> FooSingleton;
-//
-// Foo& instance = FooSingleton::instance();
-//
-// In this case, the class being managed as a singleton needs to provide an
-// initSingleton() method since the LLSingleton virtual method won't be
-// available
-//
-// As currently written, it is not thread-safe.
+// Most of the time, we want LLSingleton_manage_master() to forward its
+// methods to real LLSingletonBase methods.
+template <class T>
+struct LLSingleton_manage_master
+{
+ void add(LLSingletonBase* sb) { sb->add_master(); }
+ void remove(LLSingletonBase* sb) { sb->remove_master(); }
+ void push_initializing(LLSingletonBase* sb) { sb->push_initializing(typeid(T).name()); }
+ void pop_initializing (LLSingletonBase* sb) { sb->pop_initializing(); }
+ LLSingletonBase::list_t& get_initializing(T*) { return LLSingletonBase::get_initializing(); }
+};
+// But for the specific case of LLSingletonBase::MasterList, don't.
+template <>
+struct LLSingleton_manage_master<LLSingletonBase::MasterList>
+{
+ void add(LLSingletonBase*) {}
+ void remove(LLSingletonBase*) {}
+ void push_initializing(LLSingletonBase*) {}
+ void pop_initializing (LLSingletonBase*) {}
+ LLSingletonBase::list_t& get_initializing(LLSingletonBase::MasterList* instance)
+ {
+ return LLSingletonBase::get_initializing_from(instance);
+ }
+};
+
+// Now we can implement LLSingletonBase's template constructor.
template <typename DERIVED_TYPE>
-class LLSingleton : private boost::noncopyable
+LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>):
+ mCleaned(false),
+ mDeleteSingleton(NULL)
+{
+ // Make this the currently-initializing LLSingleton.
+ LLSingleton_manage_master<DERIVED_TYPE>().push_initializing(this);
+}
+
+/**
+ * LLSingleton implements the getInstance() method part of the Singleton
+ * pattern. It can't make the derived class constructors protected, though, so
+ * you have to do that yourself.
+ *
+ * Derive your class from LLSingleton, passing your subclass name as
+ * LLSingleton's template parameter, like so:
+ *
+ * class Foo: public LLSingleton<Foo>
+ * {
+ * // use this macro at start of every LLSingleton subclass
+ * LLSINGLETON(Foo);
+ * public:
+ * // ...
+ * };
+ *
+ * Foo& instance = Foo::instance();
+ *
+ * LLSingleton recognizes a couple special methods in your derived class.
+ *
+ * If you override LLSingleton<T>::initSingleton(), your method will be called
+ * immediately after the instance is constructed. This is useful for breaking
+ * circular dependencies: if you find that your LLSingleton subclass
+ * constructor references other LLSingleton subclass instances in a chain
+ * leading back to yours, move the instance reference from your constructor to
+ * your initSingleton() method.
+ *
+ * If you override LLSingleton<T>::cleanupSingleton(), your method will be
+ * called if someone calls LLSingletonBase::cleanupAll(). The significant part
+ * of this promise is that cleanupAll() will call individual
+ * cleanupSingleton() methods in reverse dependency order.
+ *
+ * That is, consider LLSingleton subclasses C, B and A. A depends on B, which
+ * in turn depends on C. These dependencies are expressed as calls to
+ * B::instance() or B::getInstance(), and C::instance() or C::getInstance().
+ * It shouldn't matter whether these calls appear in A::A() or
+ * A::initSingleton(), likewise B::B() or B::initSingleton().
+ *
+ * We promise that if you later call LLSingletonBase::cleanupAll():
+ * 1. A::cleanupSingleton() will be called before
+ * 2. B::cleanupSingleton(), which will be called before
+ * 3. C::cleanupSingleton().
+ * Put differently, if your LLSingleton subclass constructor or
+ * initSingleton() method explicitly depends on some other LLSingleton
+ * subclass, you may continue to rely on that other subclass in your
+ * cleanupSingleton() method.
+ *
+ * We introduce a special cleanupSingleton() method because cleanupSingleton()
+ * operations can involve nontrivial realtime, or might throw an exception. A
+ * destructor should do neither!
+ *
+ * If your cleanupSingleton() method throws an exception, we log that
+ * exception but proceed with the remaining cleanupSingleton() calls.
+ *
+ * Similarly, if at some point you call LLSingletonBase::deleteAll(), all
+ * remaining LLSingleton instances will be destroyed in dependency order. (Or
+ * call MySubclass::deleteSingleton() to specifically destroy the canonical
+ * MySubclass instance.)
+ *
+ * As currently written, LLSingleton is not thread-safe.
+ */
+template <typename DERIVED_TYPE>
+class LLSingleton : public LLSingletonBase
{
-
private:
- typedef enum e_init_state
- {
- UNINITIALIZED,
- CONSTRUCTING,
- INITIALIZING,
- INITIALIZED,
- DELETED
- } EInitState;
-
static DERIVED_TYPE* constructSingleton()
{
return new DERIVED_TYPE();
}
-
- // stores pointer to singleton instance
- struct SingletonLifetimeManager
- {
- SingletonLifetimeManager()
- {
- construct();
- }
-
- static void construct()
- {
- sData.mInitState = CONSTRUCTING;
- sData.mInstance = constructSingleton();
- sData.mInitState = INITIALIZING;
- }
-
- ~SingletonLifetimeManager()
- {
- if (sData.mInitState != DELETED)
- {
- deleteSingleton();
- }
- }
- };
-
+
+ // We know of no way to instruct the compiler that every subclass
+ // constructor MUST be private. However, we can make the LLSINGLETON()
+ // macro both declare a private constructor and provide the required
+ // friend declaration. How can we ensure that every subclass uses
+ // LLSINGLETON()? By making that macro provide a definition for this pure
+ // virtual method. If you get "can't instantiate class due to missing pure
+ // virtual method" for this method, then add LLSINGLETON(yourclass) in the
+ // subclass body.
+ virtual void you_must_use_LLSINGLETON_macro() = 0;
+
+ // stores pointer to singleton instance
+ struct SingletonLifetimeManager
+ {
+ SingletonLifetimeManager():
+ mMasterRefcount(LLSingletonBase::get_master_refcount())
+ {
+ construct();
+ }
+
+ static void construct()
+ {
+ sData.mInitState = CONSTRUCTING;
+ sData.mInstance = constructSingleton();
+ sData.mInitState = INITIALIZING;
+ }
+
+ ~SingletonLifetimeManager()
+ {
+ // The dependencies between LLSingletons, and the arbitrary order
+ // of static-object destruction, mean that we DO NOT WANT this
+ // destructor to delete this LLSingleton. This destructor will run
+ // without regard to any other LLSingleton whose cleanup might
+ // depend on its existence. What we really want is to count the
+ // runtime's attempts to cleanup LLSingleton static data -- and on
+ // the very last one, call LLSingletonBase::deleteAll(). That
+ // method will properly honor cross-LLSingleton dependencies. This
+ // is why we store an intrusive_ptr to a MasterRefcount: our
+ // ref_ptr_t member counts SingletonLifetimeManager instances.
+ // Once the runtime destroys the last of these, THEN we can delete
+ // every remaining LLSingleton.
+ }
+
+ LLSingletonBase::ref_ptr_t mMasterRefcount;
+ };
+
+protected:
+ // Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because,
+ // until our subclass constructor completes, *this isn't yet a
+ // full-fledged DERIVED_TYPE.
+ LLSingleton(): LLSingletonBase(LLSingletonBase::tag<DERIVED_TYPE>())
+ {
+ // populate base-class function pointer with the static
+ // deleteSingleton() function for this particular specialization
+ mDeleteSingleton = &deleteSingleton;
+
+ // add this new instance to the master list
+ LLSingleton_manage_master<DERIVED_TYPE>().add(this);
+ }
+
public:
- virtual ~LLSingleton()
- {
- sData.mInstance = NULL;
- sData.mInitState = DELETED;
- }
-
- /**
- * @brief Immediately delete the singleton.
- *
- * A subsequent call to LLProxy::getInstance() will construct a new
- * instance of the class.
- *
- * LLSingletons are normally destroyed after main() has exited and the C++
- * runtime is cleaning up statically-constructed objects. Some classes
- * derived from LLSingleton have objects that are part of a runtime system
- * that is terminated before main() exits. Calling the destructor of those
- * objects after the termination of their respective systems can cause
- * crashes and other problems during termination of the project. Using this
- * method to destroy the singleton early can prevent these crashes.
- *
- * An example where this is needed is for a LLSingleton that has an APR
- * object as a member that makes APR calls on destruction. The APR system is
- * shut down explicitly before main() exits. This causes a crash on exit.
- * Using this method before the call to apr_terminate() and NOT calling
- * getInstance() again will prevent the crash.
- */
- static void deleteSingleton()
- {
- delete sData.mInstance;
- sData.mInstance = NULL;
- sData.mInitState = DELETED;
- }
-
-
- static DERIVED_TYPE* getInstance()
- {
- static SingletonLifetimeManager sLifeTimeMgr;
-
- switch (sData.mInitState)
- {
- case UNINITIALIZED:
- // should never be uninitialized at this point
- llassert(false);
- return NULL;
- case CONSTRUCTING:
- LL_ERRS() << "Tried to access singleton " << typeid(DERIVED_TYPE).name() << " from singleton constructor!" << LL_ENDL;
- return NULL;
- case INITIALIZING:
- // go ahead and flag ourselves as initialized so we can be reentrant during initialization
- sData.mInitState = INITIALIZED;
- // initialize singleton after constructing it so that it can reference other singletons which in turn depend on it,
- // thus breaking cyclic dependencies
- sData.mInstance->initSingleton();
- return sData.mInstance;
- case INITIALIZED:
- return sData.mInstance;
- case DELETED:
- LL_WARNS() << "Trying to access deleted singleton " << typeid(DERIVED_TYPE).name() << " creating new instance" << LL_ENDL;
- SingletonLifetimeManager::construct();
- // same as first time construction
- sData.mInitState = INITIALIZED;
- sData.mInstance->initSingleton();
- return sData.mInstance;
- }
-
- return NULL;
- }
-
- static DERIVED_TYPE* getIfExists()
- {
- return sData.mInstance;
- }
-
- // Reference version of getInstance()
- // Preferred over getInstance() as it disallows checking for NULL
- static DERIVED_TYPE& instance()
- {
- return *getInstance();
- }
-
- // Has this singleton been created uet?
- // Use this to avoid accessing singletons before the can safely be constructed
- static bool instanceExists()
- {
- return sData.mInitState == INITIALIZED;
- }
-
- // Has this singleton already been deleted?
- // Use this to avoid accessing singletons from a static object's destructor
- static bool destroyed()
- {
- return sData.mInitState == DELETED;
- }
+ virtual ~LLSingleton()
+ {
+ // remove this instance from the master list
+ LLSingleton_manage_master<DERIVED_TYPE>().remove(this);
+ sData.mInstance = NULL;
+ sData.mInitState = DELETED;
+ }
-private:
+ /**
+ * @brief Immediately delete the singleton.
+ *
+ * A subsequent call to LLProxy::getInstance() will construct a new
+ * instance of the class.
+ *
+ * Without an explicit call to LLSingletonBase::deleteAll(), LLSingletons
+ * are implicitly destroyed after main() has exited and the C++ runtime is
+ * cleaning up statically-constructed objects. Some classes derived from
+ * LLSingleton have objects that are part of a runtime system that is
+ * terminated before main() exits. Calling the destructor of those objects
+ * after the termination of their respective systems can cause crashes and
+ * other problems during termination of the project. Using this method to
+ * destroy the singleton early can prevent these crashes.
+ *
+ * An example where this is needed is for a LLSingleton that has an APR
+ * object as a member that makes APR calls on destruction. The APR system is
+ * shut down explicitly before main() exits. This causes a crash on exit.
+ * Using this method before the call to apr_terminate() and NOT calling
+ * getInstance() again will prevent the crash.
+ */
+ static void deleteSingleton()
+ {
+ delete sData.mInstance;
+ sData.mInstance = NULL;
+ sData.mInitState = DELETED;
+ }
+
+ static DERIVED_TYPE* getInstance()
+ {
+ static SingletonLifetimeManager sLifeTimeMgr;
- virtual void initSingleton() {}
+ switch (sData.mInitState)
+ {
+ case UNINITIALIZED:
+ // should never be uninitialized at this point
+ logerrs("Uninitialized singleton ",
+ demangle(typeid(DERIVED_TYPE).name()).c_str());
+ return NULL;
- struct SingletonData
- {
- // explicitly has a default constructor so that member variables are zero initialized in BSS
- // and only changed by singleton logic, not constructor running during startup
- EInitState mInitState;
- DERIVED_TYPE* mInstance;
- };
- static SingletonData sData;
+ case CONSTRUCTING:
+ logerrs("Tried to access singleton ",
+ demangle(typeid(DERIVED_TYPE).name()).c_str(),
+ " from singleton constructor!");
+ return NULL;
+
+ case INITIALIZING:
+ // go ahead and flag ourselves as initialized so we can be
+ // reentrant during initialization
+ sData.mInitState = INITIALIZED;
+ // initialize singleton after constructing it so that it can
+ // reference other singletons which in turn depend on it, thus
+ // breaking cyclic dependencies
+ sData.mInstance->initSingleton();
+ // pop this off stack of initializing singletons
+ LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance);
+ break;
+
+ case INITIALIZED:
+ break;
+
+ case DELETED:
+ logwarns("Trying to access deleted singleton ",
+ demangle(typeid(DERIVED_TYPE).name()).c_str(),
+ " -- creating new instance");
+ SingletonLifetimeManager::construct();
+ // same as first time construction
+ sData.mInitState = INITIALIZED;
+ sData.mInstance->initSingleton();
+ // pop this off stack of initializing singletons
+ LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance);
+ break;
+ }
+
+ // By this point, if DERIVED_TYPE was pushed onto the initializing
+ // stack, it has been popped off. So the top of that stack, if any, is
+ // an LLSingleton that directly depends on DERIVED_TYPE. If this call
+ // came from another LLSingleton, rather than from vanilla application
+ // code, record the dependency.
+ sData.mInstance->capture_dependency(
+ LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(sData.mInstance),
+ sData.mInitState);
+ return sData.mInstance;
+ }
+
+ // Reference version of getInstance()
+ // Preferred over getInstance() as it disallows checking for NULL
+ static DERIVED_TYPE& instance()
+ {
+ return *getInstance();
+ }
+
+ // Has this singleton been created yet?
+ // Use this to avoid accessing singletons before they can safely be constructed.
+ static bool instanceExists()
+ {
+ return sData.mInitState == INITIALIZED;
+ }
+
+private:
+ struct SingletonData
+ {
+ // explicitly has a default constructor so that member variables are zero initialized in BSS
+ // and only changed by singleton logic, not constructor running during startup
+ EInitState mInitState;
+ DERIVED_TYPE* mInstance;
+ };
+ static SingletonData sData;
};
template<typename T>
typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;
+/**
+ * Use LLSINGLETON(Foo); at the start of an LLSingleton<Foo> subclass body
+ * when you want to declare an out-of-line constructor:
+ *
+ * @code
+ * class Foo: public LLSingleton<Foo>
+ * {
+ * // use this macro at start of every LLSingleton subclass
+ * LLSINGLETON(Foo);
+ * public:
+ * // ...
+ * };
+ * // ...
+ * [inline]
+ * Foo::Foo() { ... }
+ * @endcode
+ *
+ * Unfortunately, this mechanism does not permit you to define even a simple
+ * (but nontrivial) constructor within the class body. If it's literally
+ * trivial, use LLSINGLETON_EMPTY_CTOR(); if not, use LLSINGLETON() and define
+ * the constructor outside the class body. If you must define it in a header
+ * file, use 'inline' (unless it's a template class) to avoid duplicate-symbol
+ * errors at link time.
+ */
+#define LLSINGLETON(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() {} \
+ friend class LLSingleton<DERIVED_CLASS>; \
+ DERIVED_CLASS()
+
+/**
+ * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton<Foo>
+ * subclass body when the constructor is trivial:
+ *
+ * @code
+ * class Foo: public LLSingleton<Foo>
+ * {
+ * // use this macro at start of every LLSingleton subclass
+ * LLSINGLETON_EMPTY_CTOR(Foo);
+ * public:
+ * // ...
+ * };
+ * @endcode
+ */
+#define LLSINGLETON_EMPTY_CTOR(DERIVED_CLASS) \
+ /* LLSINGLETON() is carefully implemented to permit exactly this */ \
+ LLSINGLETON(DERIVED_CLASS) {}
+
#endif
diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h
index 0435cb8a08..b024b47225 100644
--- a/indra/llcommon/llstl.h
+++ b/indra/llcommon/llstl.h
@@ -242,7 +242,6 @@ inline T* get_ptr_in_map(const std::map<K,T*>& inmap, const K& key)
template <typename K, typename T>
inline bool is_in_map(const std::map<K,T>& inmap, const K& key)
{
- typedef typename std::map<K,T>::const_iterator map_iter;
if(inmap.find(key) == inmap.end())
{
return false;
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp
index f3b8999883..c45db3b185 100644
--- a/indra/llcommon/llstring.cpp
+++ b/indra/llcommon/llstring.cpp
@@ -576,6 +576,33 @@ std::string utf8str_truncate(const std::string& utf8str, const S32 max_len)
}
}
+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,
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index 393f6d7a8c..2fdb8be84f 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -443,7 +443,7 @@ public:
struct LLDictionaryLess
{
public:
- bool operator()(const std::string& a, const std::string& b)
+ bool operator()(const std::string& a, const std::string& b) const
{
return (LLStringUtil::precedesDict(a, b) ? true : false);
}
@@ -564,6 +564,17 @@ LL_COMMON_API S32 utf8str_compare_insensitive(
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.
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index c3f235c6ee..52255bfaeb 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -63,7 +63,7 @@ void set_thread_name( DWORD dwThreadID, const char* threadName)
__try
{
- ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );
+ ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info );
}
__except(EXCEPTION_CONTINUE_EXECUTION)
{
diff --git a/indra/llcommon/llthreadsafequeue.cpp b/indra/llcommon/llthreadsafequeue.cpp
index 185f0d63fb..491f920c0f 100644
--- a/indra/llcommon/llthreadsafequeue.cpp
+++ b/indra/llcommon/llthreadsafequeue.cpp
@@ -27,6 +27,7 @@
#include <apr_pools.h>
#include <apr_queue.h>
#include "llthreadsafequeue.h"
+#include "llexception.h"
@@ -41,13 +42,13 @@ LLThreadSafeQueueImplementation::LLThreadSafeQueueImplementation(apr_pool_t * po
{
if(mOwnsPool) {
apr_status_t status = apr_pool_create(&mPool, 0);
- if(status != APR_SUCCESS) throw LLThreadSafeQueueError("failed to allocate pool");
+ if(status != APR_SUCCESS) LLTHROW(LLThreadSafeQueueError("failed to allocate pool"));
} else {
; // No op.
}
apr_status_t status = apr_queue_create(&mQueue, capacity, mPool);
- if(status != APR_SUCCESS) throw LLThreadSafeQueueError("failed to allocate queue");
+ if(status != APR_SUCCESS) LLTHROW(LLThreadSafeQueueError("failed to allocate queue"));
}
@@ -68,9 +69,9 @@ void LLThreadSafeQueueImplementation::pushFront(void * element)
apr_status_t status = apr_queue_push(mQueue, element);
if(status == APR_EINTR) {
- throw LLThreadSafeQueueInterrupt();
+ LLTHROW(LLThreadSafeQueueInterrupt());
} else if(status != APR_SUCCESS) {
- throw LLThreadSafeQueueError("push failed");
+ LLTHROW(LLThreadSafeQueueError("push failed"));
} else {
; // Success.
}
@@ -88,9 +89,9 @@ void * LLThreadSafeQueueImplementation::popBack(void)
apr_status_t status = apr_queue_pop(mQueue, &element);
if(status == APR_EINTR) {
- throw LLThreadSafeQueueInterrupt();
+ LLTHROW(LLThreadSafeQueueInterrupt());
} else if(status != APR_SUCCESS) {
- throw LLThreadSafeQueueError("pop failed");
+ LLTHROW(LLThreadSafeQueueError("pop failed"));
} else {
return element;
}
diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h
index 58cac38769..45289ef0b4 100644
--- a/indra/llcommon/llthreadsafequeue.h
+++ b/indra/llcommon/llthreadsafequeue.h
@@ -27,9 +27,8 @@
#ifndef LL_LLTHREADSAFEQUEUE_H
#define LL_LLTHREADSAFEQUEUE_H
-
+#include "llexception.h"
#include <string>
-#include <stdexcept>
struct apr_pool_t; // From apr_pools.h
@@ -40,11 +39,11 @@ class LLThreadSafeQueueImplementation; // See below.
// A general queue exception.
//
class LL_COMMON_API LLThreadSafeQueueError:
-public std::runtime_error
+ public LLException
{
public:
LLThreadSafeQueueError(std::string const & message):
- std::runtime_error(message)
+ LLException(message)
{
; // No op.
}
diff --git a/indra/llcommon/lltracerecording.cpp b/indra/llcommon/lltracerecording.cpp
index 0b10438b9f..3094b627a2 100644
--- a/indra/llcommon/lltracerecording.cpp
+++ b/indra/llcommon/lltracerecording.cpp
@@ -257,7 +257,13 @@ F64Kilobytes Recording::getMean(const StatType<MemAccumulator>& stat)
if (active_accumulator && active_accumulator->mSize.hasValue())
{
- return F64Bytes(lerp(accumulator.mSize.getMean(), active_accumulator->mSize.getMean(), active_accumulator->mSize.getSampleCount() / (accumulator.mSize.getSampleCount() + active_accumulator->mSize.getSampleCount())));
+ F32 t = 0.0f;
+ S32 div = accumulator.mSize.getSampleCount() + active_accumulator->mSize.getSampleCount();
+ if (div > 0)
+ {
+ t = active_accumulator->mSize.getSampleCount() / div;
+ }
+ return F64Bytes(lerp(accumulator.mSize.getMean(), active_accumulator->mSize.getMean(), t));
}
else
{
@@ -426,7 +432,13 @@ F64 Recording::getMean( const StatType<SampleAccumulator>& stat )
const SampleAccumulator* active_accumulator = mActiveBuffers ? &mActiveBuffers->mSamples[stat.getIndex()] : NULL;
if (active_accumulator && active_accumulator->hasValue())
{
- return lerp(accumulator.getMean(), active_accumulator->getMean(), active_accumulator->getSampleCount() / (accumulator.getSampleCount() + active_accumulator->getSampleCount()));
+ F32 t = 0.0f;
+ S32 div = accumulator.getSampleCount() + active_accumulator->getSampleCount();
+ if (div > 0)
+ {
+ t = active_accumulator->getSampleCount() / div;
+ }
+ return lerp(accumulator.getMean(), active_accumulator->getMean(), t);
}
else
{
@@ -506,7 +518,13 @@ F64 Recording::getMean( const StatType<EventAccumulator>& stat )
const EventAccumulator* active_accumulator = mActiveBuffers ? &mActiveBuffers->mEvents[stat.getIndex()] : NULL;
if (active_accumulator && active_accumulator->hasValue())
{
- return lerp(accumulator.getMean(), active_accumulator->getMean(), active_accumulator->getSampleCount() / (accumulator.getSampleCount() + active_accumulator->getSampleCount()));
+ F32 t = 0.0f;
+ S32 div = accumulator.getSampleCount() + active_accumulator->getSampleCount();
+ if (div > 0)
+ {
+ t = active_accumulator->getSampleCount() / div;
+ }
+ return lerp(accumulator.getMean(), active_accumulator->getMean(), t);
}
else
{
diff --git a/indra/llcommon/lluriparser.cpp b/indra/llcommon/lluriparser.cpp
index 82d0dc8b4b..c275b90120 100644
--- a/indra/llcommon/lluriparser.cpp
+++ b/indra/llcommon/lluriparser.cpp
@@ -205,9 +205,9 @@ void LLUriParser::glue(std::string& uri) const
uri = first_part + second_part;
}
-void LLUriParser::glueFirst(std::string& uri) const
+void LLUriParser::glueFirst(std::string& uri, bool use_scheme) const
{
- if (mScheme.size())
+ if (use_scheme && mScheme.size())
{
uri = mScheme;
uri += "://";
diff --git a/indra/llcommon/lluriparser.h b/indra/llcommon/lluriparser.h
index 2df8085ae6..cfbf54f3c8 100644
--- a/indra/llcommon/lluriparser.h
+++ b/indra/llcommon/lluriparser.h
@@ -60,7 +60,7 @@ public:
void extractParts();
void glue(std::string& uri) const;
- void glueFirst(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();
diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp
index e3671047b4..d4af2c6b01 100644
--- a/indra/llcommon/lluuid.cpp
+++ b/indra/llcommon/lluuid.cpp
@@ -83,7 +83,7 @@ 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 ) throw bad_input_data();
+if( s == 0 ) LLTHROW(bad_input_data());
ret = ret * 85 + (s-encodeTable);
}
return ret;
diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp
index f51279e817..20de205454 100644
--- a/indra/llcommon/tests/llerror_test.cpp
+++ b/indra/llcommon/tests/llerror_test.cpp
@@ -237,8 +237,21 @@ namespace tut
void ErrorTestObject::test<4>()
// file abbreviation
{
- std::string thisFile = __FILE__;
- std::string abbreviateFile = LLError::abbreviateFile(thisFile);
+ std::string prev, abbreviateFile = __FILE__;
+ do
+ {
+ prev = abbreviateFile;
+ abbreviateFile = LLError::abbreviateFile(abbreviateFile);
+ // __FILE__ is assumed to end with
+ // indra/llcommon/tests/llerror_test.cpp. This test used to call
+ // abbreviateFile() exactly once, then check below whether it
+ // still contained the string 'indra'. That fails if the FIRST
+ // part of the pathname also contains indra! Certain developer
+ // machine images put local directory trees under
+ // /ngi-persist/indra, which is where we observe the problem. So
+ // now, keep calling abbreviateFile() until it returns its
+ // argument unchanged, THEN check.
+ } while (abbreviateFile != prev);
ensure_ends_with("file name abbreviation",
abbreviateFile,
@@ -551,9 +564,9 @@ namespace tut
function;
writeReturningLocationAndFunction(location, function);
- ensure_equals("order is location time type function message",
+ ensure_equals("order is time location type function message",
message(0),
- location + roswell() + " INFO: " + function + ": apple");
+ roswell() + " INFO: " + location + function + ": apple");
}
template<> template<>
diff --git a/indra/llcommon/tests/llexception_test.cpp b/indra/llcommon/tests/llexception_test.cpp
new file mode 100644
index 0000000000..6bee1943c2
--- /dev/null
+++ b/indra/llcommon/tests/llexception_test.cpp
@@ -0,0 +1,308 @@
+/**
+ * @file llexception_test.cpp
+ * @author Nat Goodspeed
+ * @date 2016-08-12
+ * @brief Tests for throwing exceptions.
+ *
+ * This isn't a regression test: it doesn't need to be run every build, which
+ * is why the corresponding line in llcommon/CMakeLists.txt is commented out.
+ * Rather it's a head-to-head test of what kind of exception information we
+ * can collect from various combinations of exception base classes, type of
+ * throw verb and sequences of catch clauses.
+ *
+ * This "test" makes no ensure() calls: its output goes to stdout for human
+ * examination.
+ *
+ * As of 2016-08-12 with Boost 1.57, we come to the following conclusions.
+ * These should probably be re-examined from time to time as we update Boost.
+ *
+ * - It is indisputably beneficial to use BOOST_THROW_EXCEPTION() rather than
+ * plain throw. The macro annotates the exception object with the filename,
+ * line number and function name from which the exception was thrown.
+ *
+ * - That being the case, deriving only from boost::exception isn't an option.
+ * Every exception object passed to BOOST_THROW_EXCEPTION() must be derived
+ * directly or indirectly from std::exception. The only question is whether
+ * to also derive from boost::exception. We decided to derive LLException
+ * from both, as it makes message output slightly cleaner, but this is a
+ * trivial reason: if a strong reason emerges to prefer single inheritance,
+ * dropping the boost::exception base class shouldn't be a problem.
+ *
+ * - (As you will have guessed, ridiculous things like a char* or int or a
+ * class derived from neither boost::exception nor std::exception can only
+ * be caught by that specific type or (...), and
+ * boost::current_exception_diagnostic_information() simply throws up its
+ * hands and confesses utter ignorance. Stay away from such nonsense.)
+ *
+ * - But if you derive from std::exception, to nat's surprise,
+ * boost::current_exception_diagnostic_information() gives as much
+ * information about exceptions in a catch (...) clause as you can get from
+ * a specific catch (const std::exception&) clause, notably the concrete
+ * exception class and the what() string. So instead of a sequence like
+ *
+ * try { ... }
+ * catch (const boost::exception& e) { ... boost-flavored logging ... }
+ * catch (const std::exception& e) { ... std::exception logging ... }
+ * catch (...) { ... generic logging ... }
+ *
+ * we should be able to get away with only a catch (...) clause that logs
+ * boost::current_exception_diagnostic_information().
+ *
+ * - Going further: boost::current_exception_diagnostic_information() provides
+ * just as much information even within a std::set_terminate() handler. So
+ * it might not even be strictly necessary to include a catch (...) clause
+ * since the viewer does use std::set_terminate().
+ *
+ * - (We might consider adding a catch (int) clause because Kakadu internally
+ * throws ints, and who knows if one of those might leak out. If it does,
+ * boost::current_exception_diagnostic_information() can do nothing with it.
+ * A catch (int) clause could at least log the value and rethrow.)
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llexception.h"
+// STL headers
+// std headers
+#include <typeinfo>
+// external library headers
+#include <boost/throw_exception.hpp>
+// other Linden headers
+#include "../test/lltut.h"
+
+// helper for display output
+// usage: std::cout << center(some string value, fill char, width) << std::endl;
+// (assumes it's the only thing on that particular line)
+struct center
+{
+ center(const std::string& label, char fill, std::size_t width):
+ mLabel(label),
+ mFill(fill),
+ mWidth(width)
+ {}
+
+ // Use friend declaration not because we need to grant access, but because
+ // it lets us declare a free operator like a member function.
+ friend std::ostream& operator<<(std::ostream& out, const center& ctr)
+ {
+ std::size_t padded = ctr.mLabel.length() + 2;
+ std::size_t left = (ctr.mWidth - padded) / 2;
+ std::size_t right = ctr.mWidth - left - padded;
+ return out << std::string(left, ctr.mFill) << ' ' << ctr.mLabel << ' '
+ << std::string(right, ctr.mFill);
+ }
+
+ std::string mLabel;
+ char mFill;
+ std::size_t mWidth;
+};
+
+/*****************************************************************************
+* Four kinds of exceptions: derived from boost::exception, from
+* std::exception, from both, from neither
+*****************************************************************************/
+// Interestingly, we can't use this variant with BOOST_THROW_EXCEPTION()
+// (which we want) -- we reach a failure topped by this comment:
+// //All boost exceptions are required to derive from std::exception,
+// //to ensure compatibility with BOOST_NO_EXCEPTIONS.
+struct FromBoost: public boost::exception
+{
+ FromBoost(const std::string& what): mWhat(what) {}
+ ~FromBoost() throw() {}
+ std::string what() const { return mWhat; }
+ std::string mWhat;
+};
+
+struct FromStd: public std::runtime_error
+{
+ FromStd(const std::string& what): std::runtime_error(what) {}
+};
+
+struct FromBoth: public boost::exception, public std::runtime_error
+{
+ FromBoth(const std::string& what): std::runtime_error(what) {}
+};
+
+// Same deal with FromNeither: can't use with BOOST_THROW_EXCEPTION().
+struct FromNeither
+{
+ FromNeither(const std::string& what): mWhat(what) {}
+ std::string what() const { return mWhat; }
+ std::string mWhat;
+};
+
+/*****************************************************************************
+* Two kinds of throws: plain throw and BOOST_THROW_EXCEPTION()
+*****************************************************************************/
+template <typename EXC>
+void plain_throw(const std::string& what)
+{
+ throw EXC(what);
+}
+
+template <typename EXC>
+void boost_throw(const std::string& what)
+{
+ BOOST_THROW_EXCEPTION(EXC(what));
+}
+
+// Okay, for completeness, functions that throw non-class values. We wouldn't
+// even deign to consider these if we hadn't found examples in our own source
+// code! (Note that Kakadu's internal exception support is still based on
+// throwing ints.)
+void throw_char_ptr(const std::string& what)
+{
+ throw what.c_str(); // umm...
+}
+
+void throw_int(const std::string& what)
+{
+ throw int(what.length());
+}
+
+/*****************************************************************************
+* Three sequences of catch clauses:
+* boost::exception then ...,
+* std::exception then ...,
+* or just ...
+*****************************************************************************/
+void catch_boost_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
+{
+ try
+ {
+ thrower(what);
+ }
+ catch (const boost::exception& e)
+ {
+ std::cout << "catch (const boost::exception& e)" << std::endl;
+ std::cout << "e is " << typeid(e).name() << std::endl;
+ std::cout << "boost::diagnostic_information(e):\n'"
+ << boost::diagnostic_information(e) << "'" << std::endl;
+ // no way to report e.what()
+ }
+ catch (...)
+ {
+ std::cout << "catch (...)" << std::endl;
+ std::cout << "boost::current_exception_diagnostic_information():\n'"
+ << boost::current_exception_diagnostic_information() << "'"
+ << std::endl;
+ }
+}
+
+void catch_std_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
+{
+ try
+ {
+ thrower(what);
+ }
+ catch (const std::exception& e)
+ {
+ std::cout << "catch (const std::exception& e)" << std::endl;
+ std::cout << "e is " << typeid(e).name() << std::endl;
+ std::cout << "boost::diagnostic_information(e):\n'"
+ << boost::diagnostic_information(e) << "'" << std::endl;
+ std::cout << "e.what: '"
+ << e.what() << "'" << std::endl;
+ }
+ catch (...)
+ {
+ std::cout << "catch (...)" << std::endl;
+ std::cout << "boost::current_exception_diagnostic_information():\n'"
+ << boost::current_exception_diagnostic_information() << "'"
+ << std::endl;
+ }
+}
+
+void catch_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
+{
+ try
+ {
+ thrower(what);
+ }
+ catch (...)
+ {
+ std::cout << "catch (...)" << std::endl;
+ std::cout << "boost::current_exception_diagnostic_information():\n'"
+ << boost::current_exception_diagnostic_information() << "'"
+ << std::endl;
+ }
+}
+
+/*****************************************************************************
+* Try a particular kind of throw against each of three catch sequences
+*****************************************************************************/
+void catch_several(void (*thrower)(const std::string&), const std::string& what)
+{
+ std::cout << std::string(20, '-') << "catch_boost_dotdotdot(" << what << ")" << std::endl;
+ catch_boost_dotdotdot(thrower, "catch_boost_dotdotdot(" + what + ")");
+
+ std::cout << std::string(20, '-') << "catch_std_dotdotdot(" << what << ")" << std::endl;
+ catch_std_dotdotdot(thrower, "catch_std_dotdotdot(" + what + ")");
+
+ std::cout << std::string(20, '-') << "catch_dotdotdot(" << what << ")" << std::endl;
+ catch_dotdotdot(thrower, "catch_dotdotdot(" + what + ")");
+}
+
+/*****************************************************************************
+* For a particular kind of exception, try both kinds of throw against all
+* three catch sequences
+*****************************************************************************/
+template <typename EXC>
+void catch_both_several(const std::string& what)
+{
+ std::cout << std::string(20, '*') << "plain_throw<" << what << ">" << std::endl;
+ catch_several(plain_throw<EXC>, "plain_throw<" + what + ">");
+
+ std::cout << std::string(20, '*') << "boost_throw<" << what << ">" << std::endl;
+ catch_several(boost_throw<EXC>, "boost_throw<" + what + ">");
+}
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llexception_data
+ {
+ };
+ typedef test_group<llexception_data> llexception_group;
+ typedef llexception_group::object object;
+ llexception_group llexceptiongrp("llexception");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("throwing exceptions");
+
+ // For each kind of exception, try both kinds of throw against all
+ // three catch sequences
+ std::size_t margin = 72;
+ std::cout << center("FromStd", '=', margin) << std::endl;
+ catch_both_several<FromStd>("FromStd");
+
+ std::cout << center("FromBoth", '=', margin) << std::endl;
+ catch_both_several<FromBoth>("FromBoth");
+
+ std::cout << center("FromBoost", '=', margin) << std::endl;
+ // can't throw with BOOST_THROW_EXCEPTION(), just use catch_several()
+ catch_several(plain_throw<FromBoost>, "plain_throw<FromBoost>");
+
+ std::cout << center("FromNeither", '=', margin) << std::endl;
+ // can't throw this with BOOST_THROW_EXCEPTION() either
+ catch_several(plain_throw<FromNeither>, "plain_throw<FromNeither>");
+
+ std::cout << center("const char*", '=', margin) << std::endl;
+ // We don't expect BOOST_THROW_EXCEPTION() to throw anything so daft
+ // as a const char* or an int, so don't bother with
+ // catch_both_several() -- just catch_several().
+ catch_several(throw_char_ptr, "throw_char_ptr");
+
+ std::cout << center("int", '=', margin) << std::endl;
+ catch_several(throw_int, "throw_int");
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/llheteromap_test.cpp b/indra/llcommon/tests/llheteromap_test.cpp
new file mode 100644
index 0000000000..686bffb878
--- /dev/null
+++ b/indra/llcommon/tests/llheteromap_test.cpp
@@ -0,0 +1,163 @@
+/**
+ * @file llheteromap_test.cpp
+ * @author Nat Goodspeed
+ * @date 2016-10-12
+ * @brief Test for llheteromap.
+ *
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llheteromap.h"
+// STL headers
+#include <set>
+// std headers
+// external library headers
+
+// (pacify clang)
+std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset);
+// other Linden headers
+#include "../test/lltut.h"
+
+static std::string clog;
+static std::set<std::string> dlog;
+
+// want to be able to use ensure_equals() on a set<string>
+std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset)
+{
+ out << '{';
+ const char* delim = "";
+ for (std::set<std::string>::const_iterator si(strset.begin()), se(strset.end());
+ si != se; ++si)
+ {
+ out << delim << '"' << *si << '"';
+ delim = ", ";
+ }
+ out << '}';
+ return out;
+}
+
+// unrelated test classes
+struct Chalk
+{
+ int dummy;
+ std::string name;
+
+ Chalk():
+ dummy(0)
+ {
+ clog.append("a");
+ }
+
+ ~Chalk()
+ {
+ dlog.insert("a");
+ }
+
+private:
+ Chalk(const Chalk&); // no implementation
+};
+
+struct Cheese
+{
+ std::string name;
+
+ Cheese()
+ {
+ clog.append("e");
+ }
+
+ ~Cheese()
+ {
+ dlog.insert("e");
+ }
+
+private:
+ Cheese(const Cheese&); // no implementation
+};
+
+struct Chowdah
+{
+ char displace[17];
+ std::string name;
+
+ Chowdah()
+ {
+ displace[0] = '\0';
+ clog.append("o");
+ }
+
+ ~Chowdah()
+ {
+ dlog.insert("o");
+ }
+
+private:
+ Chowdah(const Chowdah&); // no implementation
+};
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llheteromap_data
+ {
+ llheteromap_data()
+ {
+ clog.erase();
+ dlog.clear();
+ }
+ };
+ typedef test_group<llheteromap_data> llheteromap_group;
+ typedef llheteromap_group::object object;
+ llheteromap_group llheteromapgrp("llheteromap");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("create, get, delete");
+
+ {
+ LLHeteroMap map;
+
+ {
+ // create each instance
+ Chalk& chalk = map.obtain<Chalk>();
+ chalk.name = "Chalk";
+
+ Cheese& cheese = map.obtain<Cheese>();
+ cheese.name = "Cheese";
+
+ Chowdah& chowdah = map.obtain<Chowdah>();
+ chowdah.name = "Chowdah";
+ } // refs go out of scope
+
+ {
+ // verify each instance
+ Chalk& chalk = map.obtain<Chalk>();
+ ensure_equals(chalk.name, "Chalk");
+
+ Cheese& cheese = map.obtain<Cheese>();
+ ensure_equals(cheese.name, "Cheese");
+
+ Chowdah& chowdah = map.obtain<Chowdah>();
+ ensure_equals(chowdah.name, "Chowdah");
+ }
+ } // destroy map
+
+ // Chalk, Cheese and Chowdah should have been created in specific order
+ ensure_equals(clog, "aeo");
+
+ // We don't care what order they're destroyed in, as long as each is
+ // appropriately destroyed.
+ std::set<std::string> dtorset;
+ for (const char* cp = "aeo"; *cp; ++cp)
+ dtorset.insert(std::string(1, *cp));
+ ensure_equals(dlog, dtorset);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index d342dece84..c387da6c48 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -110,10 +110,7 @@ namespace tut
// finding indra/lib/python. Use our __FILE__, with
// raw-string syntax to deal with Windows pathnames.
"mydir = os.path.dirname(r'" << __FILE__ << "')\n"
- // We expect mydir to be .../indra/llcommon/tests.
- "sys.path.insert(0,\n"
- " os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n"
- "from indra.base import llsd\n"
+ "from llbase import llsd\n"
"\n"
"class ProtocolError(Exception):\n"
" def __init__(self, msg, data):\n"
diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp
new file mode 100644
index 0000000000..2f4915ce11
--- /dev/null
+++ b/indra/llcommon/tests/llpounceable_test.cpp
@@ -0,0 +1,230 @@
+/**
+ * @file llpounceable_test.cpp
+ * @author Nat Goodspeed
+ * @date 2015-05-22
+ * @brief Test for llpounceable.
+ *
+ * $LicenseInfo:firstyear=2015&license=viewerlgpl$
+ * Copyright (c) 2015, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llpounceable.h"
+// STL headers
+// std headers
+// external library headers
+#include <boost/bind.hpp>
+// other Linden headers
+#include "../test/lltut.h"
+
+/*----------------------------- string testing -----------------------------*/
+void append(std::string* dest, const std::string& src)
+{
+ dest->append(src);
+}
+
+/*-------------------------- Data-struct testing ---------------------------*/
+struct Data
+{
+ Data(const std::string& data):
+ mData(data)
+ {}
+ const std::string mData;
+};
+
+void setter(Data** dest, Data* ptr)
+{
+ *dest = ptr;
+}
+
+static Data* static_check = 0;
+
+// Set up an extern pointer to an LLPounceableStatic so the linker will fill
+// in the forward reference from below, before runtime.
+extern LLPounceable<Data*, LLPounceableStatic> gForward;
+
+struct EnqueueCall
+{
+ EnqueueCall()
+ {
+ // Intentionally use a forward reference to an LLPounceableStatic that
+ // we believe is NOT YET CONSTRUCTED. This models the scenario in
+ // which a constructor in another translation unit runs before
+ // constructors in this one. We very specifically want callWhenReady()
+ // to work even in that case: we need the LLPounceableQueueImpl to be
+ // initialized even if the LLPounceable itself is not.
+ gForward.callWhenReady(boost::bind(setter, &static_check, _1));
+ }
+} nqcall;
+// When this declaration is processed, we should enqueue the
+// setter(&static_check, _1) call for when gForward is set non-NULL. Needless
+// to remark, we want this call not to crash.
+
+// Now declare gForward. Its constructor should not run until after nqcall's.
+LLPounceable<Data*, LLPounceableStatic> gForward;
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llpounceable_data
+ {
+ };
+ typedef test_group<llpounceable_data> llpounceable_group;
+ typedef llpounceable_group::object object;
+ llpounceable_group llpounceablegrp("llpounceable");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("LLPounceableStatic out-of-order test");
+ // LLPounceable<T, LLPounceableStatic>::callWhenReady() must work even
+ // before LLPounceable's constructor runs. That's the whole point of
+ // implementing it with an LLSingleton queue. This models (say)
+ // LLPounceableStatic<LLMessageSystem*, LLPounceableStatic>.
+ ensure("static_check should still be null", ! static_check);
+ Data myData("test<1>");
+ gForward = &myData; // should run setter
+ ensure_equals("static_check should be &myData", static_check, &myData);
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("LLPounceableQueue different queues");
+ // We expect that LLPounceable<T, LLPounceableQueue> should have
+ // different queues because that specialization stores the queue
+ // directly in the LLPounceable instance.
+ Data *aptr = 0, *bptr = 0;
+ LLPounceable<Data*> a, b;
+ a.callWhenReady(boost::bind(setter, &aptr, _1));
+ b.callWhenReady(boost::bind(setter, &bptr, _1));
+ ensure("aptr should be null", ! aptr);
+ ensure("bptr should be null", ! bptr);
+ Data adata("a"), bdata("b");
+ a = &adata;
+ ensure_equals("aptr should be &adata", aptr, &adata);
+ // but we haven't yet set b
+ ensure("bptr should still be null", !bptr);
+ b = &bdata;
+ ensure_equals("bptr should be &bdata", bptr, &bdata);
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("LLPounceableStatic different queues");
+ // LLPounceable<T, LLPounceableStatic> should also have a distinct
+ // queue for each instance, but that engages an additional map lookup
+ // because there's only one LLSingleton for each T.
+ Data *aptr = 0, *bptr = 0;
+ LLPounceable<Data*, LLPounceableStatic> a, b;
+ a.callWhenReady(boost::bind(setter, &aptr, _1));
+ b.callWhenReady(boost::bind(setter, &bptr, _1));
+ ensure("aptr should be null", ! aptr);
+ ensure("bptr should be null", ! bptr);
+ Data adata("a"), bdata("b");
+ a = &adata;
+ ensure_equals("aptr should be &adata", aptr, &adata);
+ // but we haven't yet set b
+ ensure("bptr should still be null", !bptr);
+ b = &bdata;
+ ensure_equals("bptr should be &bdata", bptr, &bdata);
+ }
+
+ template<> template<>
+ void object::test<4>()
+ {
+ set_test_name("LLPounceable<T> looks like T");
+ // We want LLPounceable<T, TAG> to be drop-in replaceable for a plain
+ // T for read constructs. In particular, it should behave like a dumb
+ // pointer -- and with zero abstraction cost for such usage.
+ Data* aptr = 0;
+ Data a("a");
+ // should be able to initialize a pounceable (when its constructor
+ // runs)
+ LLPounceable<Data*> pounceable(&a);
+ // should be able to pass LLPounceable<T> to function accepting T
+ setter(&aptr, pounceable);
+ ensure_equals("aptr should be &a", aptr, &a);
+ // should be able to dereference with *
+ ensure_equals("deref with *", (*pounceable).mData, "a");
+ // should be able to dereference with ->
+ ensure_equals("deref with ->", pounceable->mData, "a");
+ // bool operations
+ ensure("test with operator bool()", pounceable);
+ ensure("test with operator !()", ! (! pounceable));
+ }
+
+ template<> template<>
+ void object::test<5>()
+ {
+ set_test_name("Multiple callWhenReady() queue items");
+ Data *p1 = 0, *p2 = 0, *p3 = 0;
+ Data a("a");
+ LLPounceable<Data*> pounceable;
+ // queue up a couple setter() calls for later
+ pounceable.callWhenReady(boost::bind(setter, &p1, _1));
+ pounceable.callWhenReady(boost::bind(setter, &p2, _1));
+ // should still be pending
+ ensure("p1 should be null", !p1);
+ ensure("p2 should be null", !p2);
+ ensure("p3 should be null", !p3);
+ pounceable = 0;
+ // assigning a new empty value shouldn't flush the queue
+ ensure("p1 should still be null", !p1);
+ ensure("p2 should still be null", !p2);
+ ensure("p3 should still be null", !p3);
+ // using whichever syntax
+ pounceable.reset(0);
+ // try to make ensure messages distinct... tough to pin down which
+ // ensure() failed if multiple ensure() calls in the same test<n> have
+ // the same message!
+ ensure("p1 should again be null", !p1);
+ ensure("p2 should again be null", !p2);
+ ensure("p3 should again be null", !p3);
+ pounceable.reset(&a); // should flush queue
+ ensure_equals("p1 should be &a", p1, &a);
+ ensure_equals("p2 should be &a", p2, &a);
+ ensure("p3 still not set", !p3);
+ // immediate call
+ pounceable.callWhenReady(boost::bind(setter, &p3, _1));
+ ensure_equals("p3 should be &a", p3, &a);
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ set_test_name("queue order");
+ std::string data;
+ LLPounceable<std::string*> pounceable;
+ pounceable.callWhenReady(boost::bind(append, _1, "a"));
+ pounceable.callWhenReady(boost::bind(append, _1, "b"));
+ pounceable.callWhenReady(boost::bind(append, _1, "c"));
+ pounceable = &data;
+ ensure_equals("callWhenReady() must preserve chronological order",
+ data, "abc");
+
+ std::string data2;
+ pounceable = NULL;
+ pounceable.callWhenReady(boost::bind(append, _1, "d"));
+ pounceable.callWhenReady(boost::bind(append, _1, "e"));
+ pounceable.callWhenReady(boost::bind(append, _1, "f"));
+ pounceable = &data2;
+ ensure_equals("LLPounceable must reset queue when fired",
+ data2, "def");
+ }
+
+ template<> template<>
+ void object::test<7>()
+ {
+ set_test_name("compile-fail test, uncomment to check");
+ // The following declaration should fail: only LLPounceableQueue and
+ // LLPounceableStatic should work as tags.
+// LLPounceable<Data*, int> pounceable;
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index 6fbb9abfc0..81b930e1e2 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -1518,10 +1518,7 @@ namespace tut
// scanner.
import_llsd("import os.path\n"
"import sys\n"
- "sys.path.insert(0,\n"
- " os.path.join(os.path.dirname(r'" __FILE__ "'),\n"
- " os.pardir, os.pardir, 'lib', 'python'))\n"
- "from indra.base import llsd\n")
+ "from llbase import llsd\n")
{}
~TestPythonCompatible() {}
diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp
index 385289aefe..56886bc73f 100644
--- a/indra/llcommon/tests/llsingleton_test.cpp
+++ b/indra/llcommon/tests/llsingleton_test.cpp
@@ -30,47 +30,172 @@
#include "llsingleton.h"
#include "../test/lltut.h"
+
+// Capture execution sequence by appending to log string.
+std::string sLog;
+
+#define DECLARE_CLASS(CLS) \
+struct CLS: public LLSingleton<CLS> \
+{ \
+ LLSINGLETON(CLS); \
+ ~CLS(); \
+public: \
+ static enum dep_flag { \
+ DEP_NONE, /* no dependency */ \
+ DEP_CTOR, /* dependency in ctor */ \
+ DEP_INIT /* dependency in initSingleton */ \
+ } sDepFlag; \
+ \
+ void initSingleton(); \
+ void cleanupSingleton(); \
+}; \
+ \
+CLS::dep_flag CLS::sDepFlag = DEP_NONE
+
+DECLARE_CLASS(A);
+DECLARE_CLASS(B);
+
+#define DEFINE_MEMBERS(CLS, OTHER) \
+CLS::CLS() \
+{ \
+ sLog.append(#CLS); \
+ if (sDepFlag == DEP_CTOR) \
+ { \
+ (void)OTHER::instance(); \
+ } \
+} \
+ \
+void CLS::initSingleton() \
+{ \
+ sLog.append("i" #CLS); \
+ if (sDepFlag == DEP_INIT) \
+ { \
+ (void)OTHER::instance(); \
+ } \
+} \
+ \
+void CLS::cleanupSingleton() \
+{ \
+ sLog.append("x" #CLS); \
+} \
+ \
+CLS::~CLS() \
+{ \
+ sLog.append("~" #CLS); \
+}
+
+DEFINE_MEMBERS(A, B)
+DEFINE_MEMBERS(B, A)
+
namespace tut
{
- struct singleton
- {
- // We need a class created with the LLSingleton template to test with.
- class LLSingletonTest: public LLSingleton<LLSingletonTest>
- {
-
- };
- };
-
- typedef test_group<singleton> singleton_t;
- typedef singleton_t::object singleton_object_t;
- tut::singleton_t tut_singleton("LLSingleton");
-
- template<> template<>
- void singleton_object_t::test<1>()
- {
-
- }
- template<> template<>
- void singleton_object_t::test<2>()
- {
- LLSingletonTest* singleton_test = LLSingletonTest::getInstance();
- ensure(singleton_test);
- }
- template<> template<>
- void singleton_object_t::test<3>()
- {
- //Construct the instance
- LLSingletonTest::getInstance();
- ensure(LLSingletonTest::instanceExists());
-
- //Delete the instance
- LLSingletonTest::deleteSingleton();
- ensure(LLSingletonTest::destroyed());
- ensure(!LLSingletonTest::instanceExists());
-
- //Construct it again.
- LLSingletonTest* singleton_test = LLSingletonTest::getInstance();
- ensure(singleton_test);
- ensure(LLSingletonTest::instanceExists());
- }
+ struct singleton
+ {
+ // We need a class created with the LLSingleton template to test with.
+ class LLSingletonTest: public LLSingleton<LLSingletonTest>
+ {
+ LLSINGLETON_EMPTY_CTOR(LLSingletonTest);
+ };
+ };
+
+ typedef test_group<singleton> singleton_t;
+ typedef singleton_t::object singleton_object_t;
+ tut::singleton_t tut_singleton("LLSingleton");
+
+ template<> template<>
+ void singleton_object_t::test<1>()
+ {
+
+ }
+ template<> template<>
+ void singleton_object_t::test<2>()
+ {
+ LLSingletonTest* singleton_test = LLSingletonTest::getInstance();
+ ensure(singleton_test);
+ }
+
+ template<> template<>
+ void singleton_object_t::test<3>()
+ {
+ //Construct the instance
+ LLSingletonTest::getInstance();
+ ensure(LLSingletonTest::instanceExists());
+
+ //Delete the instance
+ LLSingletonTest::deleteSingleton();
+ ensure(!LLSingletonTest::instanceExists());
+
+ //Construct it again.
+ LLSingletonTest* singleton_test = LLSingletonTest::getInstance();
+ ensure(singleton_test);
+ ensure(LLSingletonTest::instanceExists());
+ }
+
+#define TESTS(CLS, OTHER, N0, N1, N2, N3) \
+ template<> template<> \
+ void singleton_object_t::test<N0>() \
+ { \
+ set_test_name("just " #CLS); \
+ CLS::sDepFlag = CLS::DEP_NONE; \
+ OTHER::sDepFlag = OTHER::DEP_NONE; \
+ sLog.clear(); \
+ \
+ (void)CLS::instance(); \
+ ensure_equals(sLog, #CLS "i" #CLS); \
+ LLSingletonBase::cleanupAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \
+ LLSingletonBase::deleteAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \
+ } \
+ \
+ template<> template<> \
+ void singleton_object_t::test<N1>() \
+ { \
+ set_test_name(#CLS " ctor depends " #OTHER); \
+ CLS::sDepFlag = CLS::DEP_CTOR; \
+ OTHER::sDepFlag = OTHER::DEP_NONE; \
+ sLog.clear(); \
+ \
+ (void)CLS::instance(); \
+ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \
+ LLSingletonBase::cleanupAll(); \
+ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \
+ LLSingletonBase::deleteAll(); \
+ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ } \
+ \
+ template<> template<> \
+ void singleton_object_t::test<N2>() \
+ { \
+ set_test_name(#CLS " init depends " #OTHER); \
+ CLS::sDepFlag = CLS::DEP_INIT; \
+ OTHER::sDepFlag = OTHER::DEP_NONE; \
+ sLog.clear(); \
+ \
+ (void)CLS::instance(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \
+ LLSingletonBase::cleanupAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \
+ LLSingletonBase::deleteAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ } \
+ \
+ template<> template<> \
+ void singleton_object_t::test<N3>() \
+ { \
+ set_test_name(#CLS " circular init"); \
+ CLS::sDepFlag = CLS::DEP_INIT; \
+ OTHER::sDepFlag = OTHER::DEP_CTOR; \
+ sLog.clear(); \
+ \
+ (void)CLS::instance(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \
+ LLSingletonBase::cleanupAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \
+ LLSingletonBase::deleteAll(); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ }
+
+ TESTS(A, B, 4, 5, 6, 7)
+ TESTS(B, A, 8, 9, 10, 11)
}
diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h
index 785197ba11..9a4bbbd630 100644
--- a/indra/llcommon/tests/wrapllerrs.h
+++ b/indra/llcommon/tests/wrapllerrs.h
@@ -35,13 +35,13 @@
#include <tut/tut.hpp>
#include "llerrorcontrol.h"
+#include "llexception.h"
#include "stringize.h"
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <list>
#include <string>
-#include <stdexcept>
// statically reference the function in test.cpp... it's short, we could
// replicate, but better to reuse
@@ -67,9 +67,9 @@ struct WrapLLErrs
LLError::restoreSettings(mPriorErrorSettings);
}
- struct FatalException: public std::runtime_error
+ struct FatalException: public LLException
{
- FatalException(const std::string& what): std::runtime_error(what) {}
+ FatalException(const std::string& what): LLException(what) {}
};
void operator()(const std::string& message)
@@ -78,7 +78,7 @@ struct WrapLLErrs
error = message;
// Also throw an appropriate exception since calling code is likely to
// assume that control won't continue beyond LL_ERRS.
- throw FatalException(message);
+ LLTHROW(FatalException(message));
}
std::string error;