summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt5
-rw-r--r--indra/llcommon/fsyspath.h74
-rwxr-xr-xindra/llcommon/hexdump.h106
-rw-r--r--indra/llcommon/llapp.cpp960
-rw-r--r--indra/llcommon/llcallbacklist.cpp550
-rw-r--r--indra/llcommon/llcallbacklist.h285
-rw-r--r--indra/llcommon/llcoros.cpp139
-rw-r--r--indra/llcommon/llcoros.h79
-rw-r--r--indra/llcommon/lldate.cpp402
-rw-r--r--indra/llcommon/lldate.h194
-rw-r--r--indra/llcommon/lldependencies.h19
-rw-r--r--indra/llcommon/llerror.cpp1723
-rw-r--r--indra/llcommon/llerrorlegacy.h32
-rw-r--r--indra/llcommon/lleventcoro.cpp53
-rw-r--r--indra/llcommon/lleventdispatcher.h38
-rw-r--r--indra/llcommon/lleventfilter.cpp178
-rw-r--r--indra/llcommon/lleventfilter.h158
-rw-r--r--indra/llcommon/llevents.cpp67
-rw-r--r--indra/llcommon/llevents.h105
-rw-r--r--indra/llcommon/lleventtimer.cpp59
-rw-r--r--indra/llcommon/lleventtimer.h107
-rw-r--r--indra/llcommon/llexception.h3
-rw-r--r--indra/llcommon/llformat.h12
-rw-r--r--indra/llcommon/llinstancetracker.h80
-rw-r--r--indra/llcommon/llleap.cpp59
-rw-r--r--indra/llcommon/llleaplistener.cpp85
-rw-r--r--indra/llcommon/llleaplistener.h29
-rw-r--r--indra/llcommon/lllivefile.cpp152
-rw-r--r--indra/llcommon/llmainthreadtask.h60
-rw-r--r--indra/llcommon/llrefcount.h157
-rw-r--r--indra/llcommon/llrun.h215
-rw-r--r--indra/llcommon/llsingleton.cpp45
-rw-r--r--indra/llcommon/llsingleton.h84
-rw-r--r--indra/llcommon/llstring.h2495
-rw-r--r--indra/llcommon/lua_function.cpp984
-rw-r--r--indra/llcommon/lua_function.h259
-rw-r--r--indra/llcommon/lualistener.cpp114
-rw-r--r--indra/llcommon/lualistener.h81
-rw-r--r--indra/llcommon/stringize.h16
-rw-r--r--indra/llcommon/tests/StringVec.h12
-rw-r--r--indra/llcommon/tests/llerror_test.cpp1389
-rw-r--r--indra/llcommon/tests/lleventcoro_test.cpp91
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp6
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp26
-rw-r--r--indra/llcommon/tests/llleap_test.cpp7
-rw-r--r--indra/llcommon/tests/llmainthreadtask_test.cpp6
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp7
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp3063
-rw-r--r--indra/llcommon/threadpool.cpp21
-rw-r--r--indra/llcommon/threadpool.h8
-rw-r--r--indra/llcommon/workqueue.cpp9
-rw-r--r--indra/llcommon/workqueue.h31
52 files changed, 8561 insertions, 6378 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index c947184dc8..aa0b66f2f4 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -108,6 +108,8 @@ set(llcommon_SOURCE_FILES
lluriparser.cpp
lluuid.cpp
llworkerthread.cpp
+ lua_function.cpp
+ lualistener.cpp
hbxxh.cpp
u64.cpp
threadpool.cpp
@@ -125,6 +127,7 @@ set(llcommon_HEADER_FILES
commoncontrol.h
ctype_workaround.h
fix_macros.h
+ fsyspath.h
function_types.h
indra_constants.h
lazyeventapi.h
@@ -250,6 +253,8 @@ set(llcommon_HEADER_FILES
llwin32headers.h
llwin32headerslean.h
llworkerthread.h
+ lua_function.h
+ lualistener.h
hbxxh.h
lockstatic.h
stdtypes.h
diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h
new file mode 100644
index 0000000000..aa4e0132bc
--- /dev/null
+++ b/indra/llcommon/fsyspath.h
@@ -0,0 +1,74 @@
+/**
+ * @file fsyspath.h
+ * @author Nat Goodspeed
+ * @date 2024-04-03
+ * @brief Adapt our UTF-8 std::strings for std::filesystem::path
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_FSYSPATH_H)
+#define LL_FSYSPATH_H
+
+#include <filesystem>
+
+// While std::filesystem::path can be directly constructed from std::string on
+// both Posix and Windows, that's not what we want on Windows. Per
+// https://en.cppreference.com/w/cpp/filesystem/path/path:
+
+// ... the method of conversion to the native character set depends on the
+// character type used by source.
+//
+// * If the source character type is char, the encoding of the source is
+// assumed to be the native narrow encoding (so no conversion takes place on
+// POSIX systems).
+// * If the source character type is char8_t, conversion from UTF-8 to native
+// filesystem encoding is used. (since C++20)
+// * If the source character type is wchar_t, the input is assumed to be the
+// native wide encoding (so no conversion takes places on Windows).
+
+// The trouble is that on Windows, from std::string ("source character type is
+// char"), the "native narrow encoding" isn't UTF-8, so file paths containing
+// non-ASCII characters get mangled.
+//
+// Once we're building with C++20, we could pass a UTF-8 std::string through a
+// vector<char8_t> to engage std::filesystem::path's own UTF-8 conversion. But
+// sigh, as of 2024-04-03 we're not yet there.
+//
+// Anyway, encapsulating the important UTF-8 conversions in our own subclass
+// allows us to migrate forward to C++20 conventions without changing
+// referencing code.
+
+class fsyspath: public std::filesystem::path
+{
+ using super = std::filesystem::path;
+
+public:
+ // default
+ fsyspath() {}
+ // construct from UTF-8 encoded std::string
+ fsyspath(const std::string& path): super(std::filesystem::u8path(path)) {}
+ // construct from UTF-8 encoded const char*
+ fsyspath(const char* path): super(std::filesystem::u8path(path)) {}
+ // construct from existing path
+ fsyspath(const super& path): super(path) {}
+
+ fsyspath& operator=(const super& p) { super::operator=(p); return *this; }
+ fsyspath& operator=(const std::string& p)
+ {
+ super::operator=(std::filesystem::u8path(p));
+ return *this;
+ }
+ fsyspath& operator=(const char* p)
+ {
+ super::operator=(std::filesystem::u8path(p));
+ return *this;
+ }
+
+ // shadow base-class string() method with UTF-8 aware method
+ std::string string() const { return super::u8string(); }
+};
+
+#endif /* ! defined(LL_FSYSPATH_H) */
diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h
new file mode 100755
index 0000000000..234168cd61
--- /dev/null
+++ b/indra/llcommon/hexdump.h
@@ -0,0 +1,106 @@
+/**
+ * @file hexdump.h
+ * @author Nat Goodspeed
+ * @date 2023-10-03
+ * @brief iostream manipulators to stream hex, or string with nonprinting chars
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Copyright (c) 2023, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_HEXDUMP_H)
+#define LL_HEXDUMP_H
+
+#include <cctype>
+#include <iomanip>
+#include <iostream>
+#include <string_view>
+
+namespace LL
+{
+
+// Format a given byte string as 2-digit hex values, no separators
+// Usage: std::cout << hexdump(somestring) << ...
+class hexdump
+{
+public:
+ hexdump(const std::string_view& data):
+ hexdump(data.data(), data.length())
+ {}
+
+ hexdump(const char* data, size_t len):
+ hexdump(reinterpret_cast<const unsigned char*>(data), len)
+ {}
+
+ hexdump(const std::vector<unsigned char>& data):
+ hexdump(data.data(), data.size())
+ {}
+
+ hexdump(const unsigned char* data, size_t len):
+ mData(data, data + len)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const hexdump& self)
+ {
+ auto oldfmt{ out.flags() };
+ auto oldfill{ out.fill() };
+ out.setf(std::ios_base::hex, std::ios_base::basefield);
+ out.fill('0');
+ for (auto c : self.mData)
+ {
+ out << std::setw(2) << unsigned(c);
+ }
+ out.setf(oldfmt, std::ios_base::basefield);
+ out.fill(oldfill);
+ return out;
+ }
+
+private:
+ std::vector<unsigned char> mData;
+};
+
+// Format a given byte string as a mix of printable characters and, for each
+// non-printable character, "\xnn"
+// Usage: std::cout << hexmix(somestring) << ...
+class hexmix
+{
+public:
+ hexmix(const std::string_view& data):
+ mData(data)
+ {}
+
+ hexmix(const char* data, size_t len):
+ mData(data, len)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const hexmix& self)
+ {
+ auto oldfmt{ out.flags() };
+ auto oldfill{ out.fill() };
+ out.setf(std::ios_base::hex, std::ios_base::basefield);
+ out.fill('0');
+ for (auto c : self.mData)
+ {
+ // std::isprint() must be passed an unsigned char!
+ if (std::isprint(static_cast<unsigned char>(c)))
+ {
+ out << c;
+ }
+ else
+ {
+ out << "\\x" << std::setw(2) << unsigned(c);
+ }
+ }
+ out.setf(oldfmt, std::ios_base::basefield);
+ out.fill(oldfill);
+ return out;
+ }
+
+private:
+ std::string mData;
+};
+
+} // namespace LL
+
+#endif /* ! defined(LL_HEXDUMP_H) */
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 9729f68d23..5722f10f62 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -1,25 +1,25 @@
-/**
+/**
* @file llapp.cpp
* @brief Implementation of the LLApp class.
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -70,8 +70,8 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *);
#else
// Called by breakpad exception handler after the minidump has been generated.
bool unix_post_minidump_callback(const char *dump_dir,
- const char *minidump_id,
- void *context, bool succeeded);
+ const char *minidump_id,
+ void *context, bool succeeded);
#endif
# if LL_DARWIN
@@ -94,7 +94,7 @@ LLApp* LLApp::sApplication = NULL;
// Allows the generation of core files for post mortem under gdb
// and disables crashlogger
-BOOL LLApp::sDisableCrashlogger = FALSE;
+BOOL LLApp::sDisableCrashlogger = FALSE;
// Local flag for whether or not to do logging in signal handlers.
//static
@@ -108,222 +108,222 @@ LLAppErrorHandler LLApp::sErrorHandler = NULL;
LLApp::LLApp()
{
- // Set our status to running
- setStatus(APP_STATUS_RUNNING);
-
- LLCommon::initClass();
+ // Set our status to running
+ setStatus(APP_STATUS_RUNNING);
- // initialize the options structure. We need to make this an array
- // because the structured data will not auto-allocate if we
- // reference an invalid location with the [] operator.
- mOptions = LLSD::emptyArray();
- LLSD sd;
- for(int i = 0; i < PRIORITY_COUNT; ++i)
- {
- mOptions.append(sd);
- }
+ LLCommon::initClass();
- // Make sure we clean up APR when we exit
- // Don't need to do this if we're cleaning up APR in the destructor
- //atexit(ll_cleanup_apr);
+ // initialize the options structure. We need to make this an array
+ // because the structured data will not auto-allocate if we
+ // reference an invalid location with the [] operator.
+ mOptions = LLSD::emptyArray();
+ LLSD sd;
+ for(int i = 0; i < PRIORITY_COUNT; ++i)
+ {
+ mOptions.append(sd);
+ }
- // Set the application to this instance.
- sApplication = this;
+ // Make sure we clean up APR when we exit
+ // Don't need to do this if we're cleaning up APR in the destructor
+ //atexit(ll_cleanup_apr);
- // initialize the buffer to write the minidump filename to
- // (this is used to avoid allocating memory in the crash handler)
- memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH);
- mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe";
+ // Set the application to this instance.
+ sApplication = this;
+
+ // initialize the buffer to write the minidump filename to
+ // (this is used to avoid allocating memory in the crash handler)
+ memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH);
+ mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe";
}
LLApp::~LLApp()
{
- // reclaim live file memory
- std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
- mLiveFiles.clear();
+ // reclaim live file memory
+ std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
+ mLiveFiles.clear();
- setStopped();
+ setStopped();
- SUBSYSTEM_CLEANUP_DBG(LLCommon);
+ SUBSYSTEM_CLEANUP_DBG(LLCommon);
}
// static
LLApp* LLApp::instance()
{
- return sApplication;
+ return sApplication;
}
LLSD LLApp::getOption(const std::string& name) const
{
- LLSD rv;
- LLSD::array_const_iterator iter = mOptions.beginArray();
- LLSD::array_const_iterator end = mOptions.endArray();
- for(; iter != end; ++iter)
- {
- rv = (*iter)[name];
- if(rv.isDefined()) break;
- }
- return rv;
+ LLSD rv;
+ LLSD::array_const_iterator iter = mOptions.beginArray();
+ LLSD::array_const_iterator end = mOptions.endArray();
+ for(; iter != end; ++iter)
+ {
+ rv = (*iter)[name];
+ if(rv.isDefined()) break;
+ }
+ return rv;
}
bool LLApp::parseCommandOptions(int argc, char** argv)
{
- LLSD commands;
- std::string name;
- std::string value;
- for(int ii = 1; ii < argc; ++ii)
- {
- if(argv[ii][0] != '-')
- {
- LL_INFOS() << "Did not find option identifier while parsing token: "
- << argv[ii] << LL_ENDL;
- return false;
- }
- int offset = 1;
- if(argv[ii][1] == '-') ++offset;
- name.assign(&argv[ii][offset]);
- if(((ii+1) >= argc) || (argv[ii+1][0] == '-'))
- {
- // we found another option after this one or we have
- // reached the end. simply record that this option was
- // found and continue.
- int flag = name.compare("logfile");
- if (0 == flag)
- {
- commands[name] = "log";
- }
- else
- {
- commands[name] = true;
- }
-
- continue;
- }
- ++ii;
- value.assign(argv[ii]);
+ LLSD commands;
+ std::string name;
+ std::string value;
+ for(int ii = 1; ii < argc; ++ii)
+ {
+ if(argv[ii][0] != '-')
+ {
+ LL_INFOS() << "Did not find option identifier while parsing token: "
+ << argv[ii] << LL_ENDL;
+ return false;
+ }
+ int offset = 1;
+ if(argv[ii][1] == '-') ++offset;
+ name.assign(&argv[ii][offset]);
+ if(((ii+1) >= argc) || (argv[ii+1][0] == '-'))
+ {
+ // we found another option after this one or we have
+ // reached the end. simply record that this option was
+ // found and continue.
+ int flag = name.compare("logfile");
+ if (0 == flag)
+ {
+ commands[name] = "log";
+ }
+ else
+ {
+ commands[name] = true;
+ }
+
+ continue;
+ }
+ ++ii;
+ value.assign(argv[ii]);
#if LL_WINDOWS
- //Windows changed command line parsing. Deal with it.
- S32 slen = value.length() - 1;
- S32 start = 0;
- S32 end = slen;
- if (argv[ii][start]=='"')start++;
- if (argv[ii][end]=='"')end--;
- if (start!=0 || end!=slen)
- {
- value = value.substr (start,end);
- }
+ //Windows changed command line parsing. Deal with it.
+ S32 slen = value.length() - 1;
+ S32 start = 0;
+ S32 end = slen;
+ if (argv[ii][start]=='"')start++;
+ if (argv[ii][end]=='"')end--;
+ if (start!=0 || end!=slen)
+ {
+ value = value.substr (start,end);
+ }
#endif
- commands[name] = value;
- }
- setOptionData(PRIORITY_COMMAND_LINE, commands);
- return true;
+ commands[name] = value;
+ }
+ setOptionData(PRIORITY_COMMAND_LINE, commands);
+ return true;
}
bool LLApp::parseCommandOptions(int argc, wchar_t** wargv)
{
- LLSD commands;
- std::string name;
- std::string value;
- for(int ii = 1; ii < argc; ++ii)
- {
- if(wargv[ii][0] != '-')
- {
- LL_INFOS() << "Did not find option identifier while parsing token: "
- << wargv[ii] << LL_ENDL;
- return false;
- }
- int offset = 1;
- if(wargv[ii][1] == '-') ++offset;
+ LLSD commands;
+ std::string name;
+ std::string value;
+ for(int ii = 1; ii < argc; ++ii)
+ {
+ if(wargv[ii][0] != '-')
+ {
+ LL_INFOS() << "Did not find option identifier while parsing token: "
+ << wargv[ii] << LL_ENDL;
+ return false;
+ }
+ int offset = 1;
+ if(wargv[ii][1] == '-') ++offset;
#if LL_WINDOWS
- name.assign(utf16str_to_utf8str(&wargv[ii][offset]));
+ name.assign(utf16str_to_utf8str(&wargv[ii][offset]));
#else
- name.assign(wstring_to_utf8str(&wargv[ii][offset]));
+ name.assign(wstring_to_utf8str(&wargv[ii][offset]));
#endif
- if(((ii+1) >= argc) || (wargv[ii+1][0] == '-'))
- {
- // we found another option after this one or we have
- // reached the end. simply record that this option was
- // found and continue.
- int flag = name.compare("logfile");
- if (0 == flag)
- {
- commands[name] = "log";
- }
- else
- {
- commands[name] = true;
- }
-
- continue;
- }
- ++ii;
+ if(((ii+1) >= argc) || (wargv[ii+1][0] == '-'))
+ {
+ // we found another option after this one or we have
+ // reached the end. simply record that this option was
+ // found and continue.
+ int flag = name.compare("logfile");
+ if (0 == flag)
+ {
+ commands[name] = "log";
+ }
+ else
+ {
+ commands[name] = true;
+ }
+
+ continue;
+ }
+ ++ii;
#if LL_WINDOWS
- value.assign(utf16str_to_utf8str((wargv[ii])));
+ value.assign(utf16str_to_utf8str((wargv[ii])));
#else
- value.assign(wstring_to_utf8str((wargv[ii])));
+ value.assign(wstring_to_utf8str((wargv[ii])));
#endif
#if LL_WINDOWS
- //Windows changed command line parsing. Deal with it.
- S32 slen = value.length() - 1;
- S32 start = 0;
- S32 end = slen;
- if (wargv[ii][start]=='"')start++;
- if (wargv[ii][end]=='"')end--;
- if (start!=0 || end!=slen)
- {
- value = value.substr (start,end);
- }
+ //Windows changed command line parsing. Deal with it.
+ S32 slen = value.length() - 1;
+ S32 start = 0;
+ S32 end = slen;
+ if (wargv[ii][start]=='"')start++;
+ if (wargv[ii][end]=='"')end--;
+ if (start!=0 || end!=slen)
+ {
+ value = value.substr (start,end);
+ }
#endif
- commands[name] = value;
- }
- setOptionData(PRIORITY_COMMAND_LINE, commands);
- return true;
+ commands[name] = value;
+ }
+ setOptionData(PRIORITY_COMMAND_LINE, commands);
+ return true;
}
void LLApp::manageLiveFile(LLLiveFile* livefile)
{
- if(!livefile) return;
- livefile->checkAndReload();
- livefile->addToEventTimer();
- mLiveFiles.push_back(livefile);
+ if(!livefile) return;
+ livefile->checkAndReload();
+ livefile->addToEventTimer();
+ mLiveFiles.push_back(livefile);
}
bool LLApp::setOptionData(OptionPriority level, LLSD data)
{
- if((level < 0)
- || (level >= PRIORITY_COUNT)
- || (data.type() != LLSD::TypeMap))
- {
- return false;
- }
- mOptions[level] = data;
- return true;
+ if((level < 0)
+ || (level >= PRIORITY_COUNT)
+ || (data.type() != LLSD::TypeMap))
+ {
+ return false;
+ }
+ mOptions[level] = data;
+ return true;
}
LLSD LLApp::getOptionData(OptionPriority level)
{
- if((level < 0) || (level >= PRIORITY_COUNT))
- {
- return LLSD();
- }
- return mOptions[level];
+ if((level < 0) || (level >= PRIORITY_COUNT))
+ {
+ return LLSD();
+ }
+ return mOptions[level];
}
void LLApp::stepFrame()
{
- LLFrameTimer::updateFrameTime();
- LLFrameTimer::updateFrameCount();
- LLEventTimer::updateClass();
- mRunner.run();
+ LLFrameTimer::updateFrameTime();
+ LLFrameTimer::updateFrameCount();
+ LLCallbackList::instance().callFunctions();
+ mRunner.run();
}
#if LL_WINDOWS
@@ -332,31 +332,31 @@ void LLApp::stepFrame()
//in-depth article on the issue may be found here: http://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesn-work/
void EnableCrashingOnCrashes()
{
- typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags);
- typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags);
- const DWORD EXCEPTION_SWALLOWING = 0x1;
-
- HMODULE kernel32 = LoadLibraryA("kernel32.dll");
- tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
- "GetProcessUserModeExceptionPolicy");
- tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
- "SetProcessUserModeExceptionPolicy");
- if (pGetPolicy && pSetPolicy)
- {
- DWORD dwFlags;
- if (pGetPolicy(&dwFlags))
- {
- // Turn off the filter
- pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
- }
- }
+ typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags);
+ typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags);
+ const DWORD EXCEPTION_SWALLOWING = 0x1;
+
+ HMODULE kernel32 = LoadLibraryA("kernel32.dll");
+ tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
+ "GetProcessUserModeExceptionPolicy");
+ tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
+ "SetProcessUserModeExceptionPolicy");
+ if (pGetPolicy && pSetPolicy)
+ {
+ DWORD dwFlags;
+ if (pGetPolicy(&dwFlags))
+ {
+ // Turn off the filter
+ pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
+ }
+ }
}
#endif
void LLApp::setupErrorHandling(bool second_instance)
{
- // Error handling is done by starting up an error handling thread, which just sleeps and
- // occasionally checks to see if the app is in an error state, and sees if it needs to be run.
+ // Error handling is done by starting up an error handling thread, which just sleeps and
+ // occasionally checks to see if the app is in an error state, and sees if it needs to be run.
#if LL_WINDOWS
@@ -377,19 +377,19 @@ void LLApp::setupErrorHandling(bool second_instance)
void LLApp::setErrorHandler(LLAppErrorHandler handler)
{
- LLApp::sErrorHandler = handler;
+ LLApp::sErrorHandler = handler;
}
// static
void LLApp::runErrorHandler()
{
- if (LLApp::sErrorHandler)
- {
- LLApp::sErrorHandler();
- }
+ if (LLApp::sErrorHandler)
+ {
+ LLApp::sErrorHandler();
+ }
- //LL_INFOS() << "App status now STOPPED" << LL_ENDL;
- LLApp::setStopped();
+ //LL_INFOS() << "App status now STOPPED" << LL_ENDL;
+ LLApp::setStopped();
}
namespace
@@ -435,14 +435,14 @@ void LLApp::setStatus(EAppStatus status)
// static
void LLApp::setError()
{
- // set app status to ERROR
- setStatus(APP_STATUS_ERROR);
+ // set app status to ERROR
+ setStatus(APP_STATUS_ERROR);
}
void LLApp::setDebugFileNames(const std::string &path)
{
- mStaticDebugFileName = path + "static_debug_info.log";
- mDynamicDebugFileName = path + "dynamic_debug_info.log";
+ mStaticDebugFileName = path + "static_debug_info.log";
+ mDynamicDebugFileName = path + "dynamic_debug_info.log";
}
void LLApp::writeMiniDump()
@@ -452,64 +452,64 @@ void LLApp::writeMiniDump()
// static
void LLApp::setQuitting()
{
- if (!isExiting())
- {
- // If we're already exiting, we don't want to reset our state back to quitting.
- LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL;
- setStatus(APP_STATUS_QUITTING);
- }
+ if (!isExiting())
+ {
+ // If we're already exiting, we don't want to reset our state back to quitting.
+ LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL;
+ setStatus(APP_STATUS_QUITTING);
+ }
}
// static
void LLApp::setStopped()
{
- setStatus(APP_STATUS_STOPPED);
+ setStatus(APP_STATUS_STOPPED);
}
// static
bool LLApp::isStopped()
{
- return (APP_STATUS_STOPPED == sStatus.get());
+ return (APP_STATUS_STOPPED == sStatus.get());
}
// static
bool LLApp::isRunning()
{
- return (APP_STATUS_RUNNING == sStatus.get());
+ return (APP_STATUS_RUNNING == sStatus.get());
}
// static
bool LLApp::isError()
{
- return (APP_STATUS_ERROR == sStatus.get());
+ return (APP_STATUS_ERROR == sStatus.get());
}
// static
bool LLApp::isQuitting()
{
- return (APP_STATUS_QUITTING == sStatus.get());
+ return (APP_STATUS_QUITTING == sStatus.get());
}
// static
bool LLApp::isExiting()
{
- return isQuitting() || isError();
+ return isQuitting() || isError();
}
void LLApp::disableCrashlogger()
{
- sDisableCrashlogger = TRUE;
+ sDisableCrashlogger = TRUE;
}
// static
bool LLApp::isCrashloggerDisabled()
{
- return (sDisableCrashlogger == TRUE);
+ return (sDisableCrashlogger == TRUE);
}
// static
@@ -518,336 +518,336 @@ int LLApp::getPid()
#if LL_WINDOWS
return GetCurrentProcessId();
#else
- return getpid();
+ return getpid();
#endif
}
#if LL_WINDOWS
LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop)
{
- // Translate the signals/exceptions into cross-platform stuff
- // Windows implementation
+ // Translate the signals/exceptions into cross-platform stuff
+ // Windows implementation
- // Make sure the user sees something to indicate that the app crashed.
- LONG retval;
+ // Make sure the user sees something to indicate that the app crashed.
+ LONG retval;
- if (LLApp::isError())
- {
- LL_WARNS() << "Got another fatal signal while in the error handler, die now!" << LL_ENDL;
- retval = EXCEPTION_EXECUTE_HANDLER;
- return retval;
- }
+ if (LLApp::isError())
+ {
+ LL_WARNS() << "Got another fatal signal while in the error handler, die now!" << LL_ENDL;
+ retval = EXCEPTION_EXECUTE_HANDLER;
+ return retval;
+ }
- // Flag status to error, so thread_error starts its work
- LLApp::setError();
+ // Flag status to error, so thread_error starts its work
+ LLApp::setError();
- // Block in the exception handler until the app has stopped
- // This is pretty sketchy, but appears to work just fine
- while (!LLApp::isStopped())
- {
- ms_sleep(10);
- }
+ // Block in the exception handler until the app has stopped
+ // This is pretty sketchy, but appears to work just fine
+ while (!LLApp::isStopped())
+ {
+ ms_sleep(10);
+ }
- //
- // Generate a minidump if we can.
- //
- // TODO: This needs to be ported over form the viewer-specific
- // LLWinDebug class
+ //
+ // Generate a minidump if we can.
+ //
+ // TODO: This needs to be ported over form the viewer-specific
+ // LLWinDebug class
- //
- // At this point, we always want to exit the app. There's no graceful
- // recovery for an unhandled exception.
- //
- // Just kill the process.
- retval = EXCEPTION_EXECUTE_HANDLER;
- return retval;
+ //
+ // At this point, we always want to exit the app. There's no graceful
+ // recovery for an unhandled exception.
+ //
+ // Just kill the process.
+ retval = EXCEPTION_EXECUTE_HANDLER;
+ return retval;
}
// Win32 doesn't support signals. This is used instead.
-BOOL ConsoleCtrlHandler(DWORD fdwCtrlType)
-{
- switch (fdwCtrlType)
- {
- case CTRL_BREAK_EVENT:
- case CTRL_LOGOFF_EVENT:
- case CTRL_SHUTDOWN_EVENT:
- case CTRL_CLOSE_EVENT: // From end task or the window close button.
- case CTRL_C_EVENT: // from CTRL-C on the keyboard
- // Just set our state to quitting, not error
- if (LLApp::isQuitting() || LLApp::isError())
- {
- // We're already trying to die, just ignore this signal
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
- }
- return TRUE;
- }
- LLApp::setQuitting();
- return TRUE;
-
- default:
- return FALSE;
- }
-}
+BOOL ConsoleCtrlHandler(DWORD fdwCtrlType)
+{
+ switch (fdwCtrlType)
+ {
+ case CTRL_BREAK_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ case CTRL_CLOSE_EVENT: // From end task or the window close button.
+ case CTRL_C_EVENT: // from CTRL-C on the keyboard
+ // Just set our state to quitting, not error
+ if (LLApp::isQuitting() || LLApp::isError())
+ {
+ // We're already trying to die, just ignore this signal
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
+ }
+ return TRUE;
+ }
+ LLApp::setQuitting();
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
#else //!LL_WINDOWS
void setup_signals()
{
- //
- // Set up signal handlers that may result in program termination
- //
- struct sigaction act;
- act.sa_sigaction = default_unix_signal_handler;
- sigemptyset( &act.sa_mask );
- act.sa_flags = SA_SIGINFO;
+ //
+ // Set up signal handlers that may result in program termination
+ //
+ struct sigaction act;
+ act.sa_sigaction = default_unix_signal_handler;
+ sigemptyset( &act.sa_mask );
+ act.sa_flags = SA_SIGINFO;
- // Synchronous signals
+ // Synchronous signals
# ifndef LL_BUGSPLAT
- sigaction(SIGABRT, &act, NULL);
+ sigaction(SIGABRT, &act, NULL);
# endif
- sigaction(SIGALRM, &act, NULL);
- sigaction(SIGBUS, &act, NULL);
- sigaction(SIGFPE, &act, NULL);
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGILL, &act, NULL);
- sigaction(SIGPIPE, &act, NULL);
- sigaction(SIGSEGV, &act, NULL);
- sigaction(SIGSYS, &act, NULL);
-
- sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
- sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
-
- // Asynchronous signals that are normally ignored
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGBUS, &act, NULL);
+ sigaction(SIGFPE, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGILL, &act, NULL);
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGSYS, &act, NULL);
+
+ sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
+ sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
+
+ // Asynchronous signals that are normally ignored
#ifndef LL_IGNORE_SIGCHLD
- sigaction(SIGCHLD, &act, NULL);
+ sigaction(SIGCHLD, &act, NULL);
#endif // LL_IGNORE_SIGCHLD
- sigaction(SIGUSR2, &act, NULL);
+ sigaction(SIGUSR2, &act, NULL);
- // Asynchronous signals that result in attempted graceful exit
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGTERM, &act, NULL);
- sigaction(SIGINT, &act, NULL);
-
- // Asynchronous signals that result in core
- sigaction(SIGQUIT, &act, NULL);
+ // Asynchronous signals that result in attempted graceful exit
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
+ // Asynchronous signals that result in core
+ sigaction(SIGQUIT, &act, NULL);
+
}
void clear_signals()
{
- struct sigaction act;
- act.sa_handler = SIG_DFL;
- sigemptyset( &act.sa_mask );
- act.sa_flags = SA_SIGINFO;
+ struct sigaction act;
+ act.sa_handler = SIG_DFL;
+ sigemptyset( &act.sa_mask );
+ act.sa_flags = SA_SIGINFO;
- // Synchronous signals
+ // Synchronous signals
# ifndef LL_BUGSPLAT
- sigaction(SIGABRT, &act, NULL);
+ sigaction(SIGABRT, &act, NULL);
# endif
- sigaction(SIGALRM, &act, NULL);
- sigaction(SIGBUS, &act, NULL);
- sigaction(SIGFPE, &act, NULL);
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGILL, &act, NULL);
- sigaction(SIGPIPE, &act, NULL);
- sigaction(SIGSEGV, &act, NULL);
- sigaction(SIGSYS, &act, NULL);
-
- sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
- sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
-
- // Asynchronous signals that are normally ignored
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGBUS, &act, NULL);
+ sigaction(SIGFPE, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGILL, &act, NULL);
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGSYS, &act, NULL);
+
+ sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
+ sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
+
+ // Asynchronous signals that are normally ignored
#ifndef LL_IGNORE_SIGCHLD
- sigaction(SIGCHLD, &act, NULL);
+ sigaction(SIGCHLD, &act, NULL);
#endif // LL_IGNORE_SIGCHLD
- // Asynchronous signals that result in attempted graceful exit
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGTERM, &act, NULL);
- sigaction(SIGINT, &act, NULL);
+ // Asynchronous signals that result in attempted graceful exit
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
- // Asynchronous signals that result in core
- sigaction(SIGUSR2, &act, NULL);
- sigaction(SIGQUIT, &act, NULL);
+ // Asynchronous signals that result in core
+ sigaction(SIGUSR2, &act, NULL);
+ sigaction(SIGQUIT, &act, NULL);
}
void default_unix_signal_handler(int signum, siginfo_t *info, void *)
{
- // Unix implementation of synchronous signal handler
- // This runs in the thread that threw the signal.
- // We do the somewhat sketchy operation of blocking in here until the error handler
- // has gracefully stopped the app.
+ // Unix implementation of synchronous signal handler
+ // This runs in the thread that threw the signal.
+ // We do the somewhat sketchy operation of blocking in here until the error handler
+ // has gracefully stopped the app.
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL;
- }
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL;
+ }
- switch (signum)
- {
- case SIGCHLD:
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL;
- }
+ switch (signum)
+ {
+ case SIGCHLD:
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL;
+ }
- return;
- case SIGABRT:
+ return;
+ case SIGABRT:
// Note that this handler is not set for SIGABRT when using Bugsplat
- // Abort just results in termination of the app, no funky error handling.
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL;
- }
- clear_signals();
- raise(signum);
- return;
- case SIGINT:
- case SIGHUP:
- case SIGTERM:
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL;
- }
- // Graceful exit
- // Just set our state to quitting, not error
- if (LLApp::isQuitting() || LLApp::isError())
- {
- // We're already trying to die, just ignore this signal
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
- }
- return;
- }
- LLApp::setQuitting();
- return;
- case SIGALRM:
- case SIGPIPE:
- case SIGUSR2:
- default:
- if (signum == LL_SMACKDOWN_SIGNAL ||
- signum == SIGBUS ||
- signum == SIGILL ||
- signum == SIGFPE ||
- signum == SIGSEGV ||
- signum == SIGQUIT)
- {
- if (signum == LL_SMACKDOWN_SIGNAL)
- {
- // Smackdown treated just like any other app termination, for now
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL;
- }
- else
- {
- // Don't log anything, even errors - this is because this signal could happen anywhere.
- LLError::setDefaultLevel(LLError::LEVEL_NONE);
- }
-
- // Change the signal that we reraise to SIGABRT, so we generate a core dump.
- signum = SIGABRT;
- }
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL;
- }
- if (LLApp::isError())
- {
- // Received second fatal signal while handling first, just die right now
- // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app.
- clear_signals();
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL;
- }
- raise(signum);
- return;
- }
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL;
- }
-
- if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem
- {
- clear_signals();
- LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL;
- raise(signum);
- return;
- }
-
- // Flag status to ERROR
- LLApp::setError();
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL;
- }
- clear_signals();
- raise(signum);
- return;
- } else {
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
- }
- }
- }
+ // Abort just results in termination of the app, no funky error handling.
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL;
+ }
+ clear_signals();
+ raise(signum);
+ return;
+ case SIGINT:
+ case SIGHUP:
+ case SIGTERM:
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL;
+ }
+ // Graceful exit
+ // Just set our state to quitting, not error
+ if (LLApp::isQuitting() || LLApp::isError())
+ {
+ // We're already trying to die, just ignore this signal
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
+ }
+ return;
+ }
+ LLApp::setQuitting();
+ return;
+ case SIGALRM:
+ case SIGPIPE:
+ case SIGUSR2:
+ default:
+ if (signum == LL_SMACKDOWN_SIGNAL ||
+ signum == SIGBUS ||
+ signum == SIGILL ||
+ signum == SIGFPE ||
+ signum == SIGSEGV ||
+ signum == SIGQUIT)
+ {
+ if (signum == LL_SMACKDOWN_SIGNAL)
+ {
+ // Smackdown treated just like any other app termination, for now
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL;
+ }
+ else
+ {
+ // Don't log anything, even errors - this is because this signal could happen anywhere.
+ LLError::setDefaultLevel(LLError::LEVEL_NONE);
+ }
+
+ // Change the signal that we reraise to SIGABRT, so we generate a core dump.
+ signum = SIGABRT;
+ }
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL;
+ }
+ if (LLApp::isError())
+ {
+ // Received second fatal signal while handling first, just die right now
+ // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app.
+ clear_signals();
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL;
+ }
+ raise(signum);
+ return;
+ }
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL;
+ }
+
+ if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem
+ {
+ clear_signals();
+ LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL;
+ raise(signum);
+ return;
+ }
+
+ // Flag status to ERROR
+ LLApp::setError();
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL;
+ }
+ clear_signals();
+ raise(signum);
+ return;
+ } else {
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
+ }
+ }
+ }
}
#if LL_LINUX
#endif
bool unix_post_minidump_callback(const char *dump_dir,
- const char *minidump_id,
- void *context, bool succeeded)
-{
- // Copy minidump file path into fixed buffer in the app instance to avoid
- // heap allocations in a crash handler.
-
- // path format: <dump_dir>/<minidump_id>.dmp
- auto dirPathLength = strlen(dump_dir);
- auto idLength = strlen(minidump_id);
-
- // The path must not be truncated.
- llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH);
-
- char * path = LLApp::instance()->getMiniDumpFilename();
- auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH;
- strncpy(path, dump_dir, remaining);
- remaining -= dirPathLength;
- path += dirPathLength;
- if (remaining > 0 && dirPathLength > 0 && path[-1] != '/')
- {
- *path++ = '/';
- --remaining;
- }
- if (remaining > 0)
- {
- strncpy(path, minidump_id, remaining);
- remaining -= idLength;
- path += idLength;
- strncpy(path, ".dmp", remaining);
- }
-
- LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL;
- LLApp::runErrorHandler();
-
+ const char *minidump_id,
+ void *context, bool succeeded)
+{
+ // Copy minidump file path into fixed buffer in the app instance to avoid
+ // heap allocations in a crash handler.
+
+ // path format: <dump_dir>/<minidump_id>.dmp
+ auto dirPathLength = strlen(dump_dir);
+ auto idLength = strlen(minidump_id);
+
+ // The path must not be truncated.
+ llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH);
+
+ char * path = LLApp::instance()->getMiniDumpFilename();
+ auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH;
+ strncpy(path, dump_dir, remaining);
+ remaining -= dirPathLength;
+ path += dirPathLength;
+ if (remaining > 0 && dirPathLength > 0 && path[-1] != '/')
+ {
+ *path++ = '/';
+ --remaining;
+ }
+ if (remaining > 0)
+ {
+ strncpy(path, minidump_id, remaining);
+ remaining -= idLength;
+ path += idLength;
+ strncpy(path, ".dmp", remaining);
+ }
+
+ LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL;
+ LLApp::runErrorHandler();
+
#ifndef LL_RELEASE_FOR_DOWNLOAD
- clear_signals();
- return false;
+ clear_signals();
+ return false;
#else
- return true;
+ return true;
#endif
}
#endif // !WINDOWS
diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp
index b5a58e90b3..c89f7d12b2 100644
--- a/indra/llcommon/llcallbacklist.cpp
+++ b/indra/llcommon/llcallbacklist.cpp
@@ -1,230 +1,530 @@
-/**
+/**
* @file llcallbacklist.cpp
* @brief A simple list of callback functions to call.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
+#include "lazyeventapi.h"
#include "llcallbacklist.h"
-#include "lleventtimer.h"
-#include "llerrorlegacy.h"
-
-// Globals
-//
-LLCallbackList gIdleCallbacks;
+#include "llexception.h"
+#include "llsdutil.h"
+#include <vector>
//
// Member functions
//
+/*****************************************************************************
+* LLCallbackList
+*****************************************************************************/
LLCallbackList::LLCallbackList()
{
- // nothing
+ // nothing
}
LLCallbackList::~LLCallbackList()
{
}
-
-void LLCallbackList::addFunction( callback_t func, void *data)
+LLCallbackList::handle_t LLCallbackList::addFunction( callback_t func, void *data)
{
- if (!func)
- {
- return;
- }
+ if (!func)
+ {
+ return {};
+ }
- // only add one callback per func/data pair
- //
- if (containsFunction(func, data))
- {
- return;
- }
+ // only add one callback per func/data pair
+ //
+ if (containsFunction(func, data))
+ {
+ return {};
+ }
- callback_pair_t t(func, data);
- mCallbackList.push_back(t);
+ auto handle = addFunction([func, data]{ func(data); });
+ mLookup.emplace(callback_pair_t(func, data), handle);
+ return handle;
+}
+
+LLCallbackList::handle_t LLCallbackList::addFunction( const callable_t& func )
+{
+ return mCallbackList.connect(func);
}
bool LLCallbackList::containsFunction( callback_t func, void *data)
{
- callback_pair_t t(func, data);
- callback_list_t::iterator iter = find(func,data);
- if (iter != mCallbackList.end())
- {
- return TRUE;
- }
- else
+ return mLookup.find(callback_pair_t(func, data)) != mLookup.end();
+}
+
+bool LLCallbackList::deleteFunction( callback_t func, void *data)
+{
+ auto found = mLookup.find(callback_pair_t(func, data));
+ if (found != mLookup.end())
+ {
+ mLookup.erase(found);
+ deleteFunction(found->second);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void LLCallbackList::deleteFunction( const handle_t& handle )
+{
+ handle.disconnect();
+}
+
+void LLCallbackList::deleteAllFunctions()
+{
+ mCallbackList = {};
+ mLookup.clear();
+}
+
+void LLCallbackList::callFunctions()
+{
+ mCallbackList();
+}
+
+LLCallbackList::handle_t LLCallbackList::doOnIdleOneTime( const callable_t& func )
+{
+ // connect_extended() passes the connection to the callback
+ return mCallbackList.connect_extended(
+ [func](const handle_t& handle)
+ {
+ handle.disconnect();
+ func();
+ });
+}
+
+LLCallbackList::handle_t LLCallbackList::doOnIdleRepeating( const bool_func_t& func )
+{
+ return mCallbackList.connect_extended(
+ [func](const handle_t& handle)
+ {
+ if (func())
+ {
+ handle.disconnect();
+ }
+ });
+}
+
+/*****************************************************************************
+* LL::Timers
+*****************************************************************************/
+namespace LL
+{
+
+Timers::Timers() {}
+
+// Call a given callable once at specified timestamp.
+Timers::handle_t Timers::scheduleAt(nullary_func_t callable, LLDate::timestamp time)
+{
+ // tick() assumes you want to run periodically until you return true.
+ // Schedule a task that returns true after a single call.
+ return scheduleAtEvery(once(callable), time, 0);
+}
+
+// Call a given callable once after specified interval.
+Timers::handle_t Timers::scheduleAfter(nullary_func_t callable, F32 seconds)
+{
+ return scheduleEvery(once(callable), seconds);
+}
+
+// Call a given callable every specified number of seconds, until it returns true.
+Timers::handle_t Timers::scheduleEvery(bool_func_t callable, F32 seconds)
+{
+ return scheduleAtEvery(callable, now() + seconds, seconds);
+}
+
+Timers::handle_t Timers::scheduleAtEvery(bool_func_t callable,
+ LLDate::timestamp time, F32 interval)
+{
+ // Pick token FIRST to store a self-reference in mQueue's managed node as
+ // well as in mMeta. Pre-increment to distinguish 0 from any live
+ // handle_t.
+ token_t token{ ++mToken };
+ // For the moment, store a default-constructed mQueue handle --
+ // we'll fill in later.
+ auto [iter, inserted] = mMeta.emplace(token,
+ Metadata{ queue_t::handle_type(), time, interval });
+ // It's important that our token is unique.
+ llassert(inserted);
+
+ // Remember whether this is the first entry in mQueue
+ bool first{ mQueue.empty() };
+ auto handle{ mQueue.emplace(callable, token, time) };
+ // Now that we have an mQueue handle_type, store it in mMeta entry.
+ iter->second.mHandle = handle;
+ if (first && ! mLive.connected())
{
- return FALSE;
+ // If this is our first entry, register for regular callbacks.
+ mLive = LLCallbackList::instance().doOnIdleRepeating([this]{ return tick(); });
}
+ // Make an Timers::handle_t from token.
+ return { token };
}
+bool Timers::isRunning(handle_t timer) const
+{
+ // A default-constructed timer isn't running.
+ // A timer we don't find in mMeta has fired or been canceled.
+ return timer && mMeta.find(timer.token) != mMeta.end();
+}
-bool LLCallbackList::deleteFunction( callback_t func, void *data)
+F32 Timers::timeUntilCall(handle_t timer) const
{
- callback_list_t::iterator iter = find(func,data);
- if (iter != mCallbackList.end())
+ MetaMap::const_iterator found;
+ if ((! timer) || (found = mMeta.find(timer.token)) == mMeta.end())
{
- mCallbackList.erase(iter);
- return TRUE;
+ return 0.f;
}
else
{
- return FALSE;
+ return found->second.mTime - now();
}
}
-inline
-LLCallbackList::callback_list_t::iterator
-LLCallbackList::find(callback_t func, void *data)
+// Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleEvery()
+bool Timers::cancel(handle_t& timer)
{
- callback_pair_t t(func, data);
- return std::find(mCallbackList.begin(), mCallbackList.end(), t);
+ // For exception safety, capture and clear timer before canceling.
+ // Once we've canceled this handle, don't retain the live handle.
+ const handle_t ctimer{ timer };
+ timer = handle_t();
+ return cancel(ctimer);
}
-void LLCallbackList::deleteAllFunctions()
+bool Timers::cancel(const handle_t& timer)
{
- mCallbackList.clear();
-}
+ if (! timer)
+ {
+ return false;
+ }
+ // fibonacci_heap documentation does not address the question of what
+ // happens if you call erase() twice with the same handle. Is it a no-op?
+ // Does it invalidate the heap? Is it UB?
-void LLCallbackList::callFunctions()
-{
- for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); )
+ // Nor do we find any documented way to ask whether a given handle still
+ // tracks a valid heap node. That's why we capture all returned handles in
+ // mMeta and validate against that collection. What about the pop()
+ // call in tick()? How to map from the top() value back to the
+ // corresponding handle_t? That's why we store func_at::mToken.
+
+ // fibonacci_heap provides a pair of begin()/end() methods to iterate over
+ // all nodes (NOT in heap order), plus a function to convert from such
+ // iterators to handles. Without mMeta, that would be our only chance
+ // to validate.
+ auto found{ mMeta.find(timer.token) };
+ if (found == mMeta.end())
+ {
+ // we don't recognize this handle -- maybe the timer has already
+ // fired, maybe it was previously canceled.
+ return false;
+ }
+
+ // Funny case: what if the callback directly or indirectly reaches a
+ // cancel() call for its own handle?
+ if (found->second.mRunning)
+ {
+ // tick() has special logic to defer the actual deletion until the
+ // callback has returned
+ found->second.mCancel = true;
+ // this handle does in fact reference a live timer,
+ // which we're going to cancel when we get a chance
+ return true;
+ }
+
+ // Erase from mQueue the handle_type referenced by timer.token.
+ mQueue.erase(found->second.mHandle);
+ // before erasing the mMeta entry
+ mMeta.erase(found);
+ if (mQueue.empty())
{
- callback_list_t::iterator curiter = iter++;
- curiter->first(curiter->second);
+ // If that was the last active timer, unregister for callbacks.
+ //LLCallbackList::instance().deleteFunction(mLive);
+ // Since we're in the source file that knows the true identity of an
+ // LLCallbackList::handle_t, we don't even need to call instance().
+ mLive.disconnect();
}
+ return true;
}
-// Shim class to allow arbitrary boost::bind
-// expressions to be run as one-time idle callbacks.
-class OnIdleCallbackOneTime
+// RAII class to set specified variable to specified value
+// only for the duration of containing scope
+template <typename VAR, typename VALUE>
+class TempSet
{
public:
- OnIdleCallbackOneTime(nullary_func_t callable):
- mCallable(callable)
+ TempSet(VAR& var, const VALUE& value):
+ mVar(var),
+ mOldValue(mVar)
{
+ mVar = value;
}
- static void onIdle(void *data)
- {
- gIdleCallbacks.deleteFunction(onIdle, data);
- OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data);
- self->call();
- delete self;
- }
- void call()
+
+ TempSet(const TempSet&) = delete;
+ TempSet& operator=(const TempSet&) = delete;
+
+ ~TempSet()
{
- mCallable();
+ mVar = mOldValue;
}
+
private:
- nullary_func_t mCallable;
+ VAR& mVar;
+ VALUE mOldValue;
};
-void doOnIdleOneTime(nullary_func_t callable)
+bool Timers::tick()
{
- OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
-}
+ // Fetch current time only on entry, even though running some mQueue task
+ // may take long enough that the next one after would become ready. We're
+ // sharing this thread with everything else, and there's a risk we might
+ // starve it if we have a sequence of tasks that take nontrivial time.
+ auto now{ LLDate::now().secondsSinceEpoch() };
+ auto cutoff{ now + TIMESLICE };
-// Shim class to allow generic boost functions to be run as
-// recurring idle callbacks. Callable should return true when done,
-// false to continue getting called.
-class OnIdleCallbackRepeating
-{
-public:
- OnIdleCallbackRepeating(bool_func_t callable):
- mCallable(callable)
- {
- }
- // Will keep getting called until the callable returns true.
- static void onIdle(void *data)
+ // Capture tasks we've processed but that want to be rescheduled.
+ // Defer rescheduling them immediately to avoid getting stuck looping over
+ // a recurring task with a nonpositive interval.
+ std::vector<std::pair<MetaMap::iterator, func_at>> deferred;
+
+ while (! mQueue.empty())
{
- OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
- bool done = self->call();
- if (done)
+ auto& top{ mQueue.top() };
+ if (top.mTime > now)
+ {
+ // we've hit an entry that's still in the future:
+ // done with this tick()
+ break;
+ }
+ if (LLDate::now().secondsSinceEpoch() > cutoff)
+ {
+ // we still have ready tasks, but we've already eaten too much
+ // time this tick() -- defer until next tick()
+ break;
+ }
+
+ // Found a ready task. Look up its corresponding mMeta entry.
+ auto meta{ mMeta.find(top.mToken) };
+ llassert(meta != mMeta.end());
+ bool done;
{
- gIdleCallbacks.deleteFunction(onIdle, data);
- delete self;
+ // Mark our mMeta entry so we don't cancel this timer while its
+ // callback is running, but unmark it even in case of exception.
+ TempSet running(meta->second.mRunning, true);
+ // run the callback and capture its desire to end repetition
+ try
+ {
+ done = top.mFunc();
+ }
+ catch (...)
+ {
+ // Don't crash if a timer callable throws.
+ // But don't continue calling that callable, either.
+ done = true;
+ LOG_UNHANDLED_EXCEPTION("LL::Timers");
+ }
+ } // clear mRunning
+
+ // If mFunc() returned true (all done, stop calling me) or
+ // meta->mCancel (somebody tried to cancel this timer during the
+ // callback call), then we're done: clean up both entries.
+ if (done || meta->second.mCancel)
+ {
+ // remove the mMeta entry referencing this task
+ mMeta.erase(meta);
+ }
+ else
+ {
+ // mFunc returned false, and nobody asked to cancel:
+ // continue calling this task at a future time.
+ meta->second.mTime += meta->second.mInterval;
+ // capture this task to reschedule once we break loop
+ deferred.push_back({meta, top});
+ // update func_at's mTime to match meta's
+ deferred.back().second.mTime = meta->second.mTime;
}
+ // Remove the mQueue entry regardless, or we risk stalling the
+ // queue right here if we have a nonpositive interval.
+ mQueue.pop();
}
- bool call()
+
+ // Now reschedule any tasks that need to be rescheduled.
+ for (const auto& [meta, task] : deferred)
{
- return mCallable();
+ auto handle{ mQueue.push(task) };
+ // track this new mQueue handle_type
+ meta->second.mHandle = handle;
}
-private:
- bool_func_t mCallable;
-};
-void doOnIdleRepeating(bool_func_t callable)
-{
- OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
+ // If, after all the twiddling above, our queue ended up empty,
+ // stop calling every tick.
+ return mQueue.empty();
}
-class NullaryFuncEventTimer: public LLEventTimer
+/*****************************************************************************
+* TimersListener
+*****************************************************************************/
+
+class TimersListener: public LLEventAPI
{
public:
- NullaryFuncEventTimer(nullary_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
+ TimersListener(const LazyEventAPIParams& params): LLEventAPI(params) {}
+
+ // Forbid a script from requesting callbacks too quickly.
+ static constexpr LLSD::Real MINTIMER{ 1.0 };
+
+ void scheduleAfter(const LLSD& params);
+ void scheduleEvery(const LLSD& params);
+ LLSD cancel(const LLSD& params);
+ LLSD isRunning(const LLSD& params);
+ LLSD timeUntilCall(const LLSD& params);
+
+private:
+ using HandleMap = std::unordered_map<LLSD::Integer, Timers::temp_handle_t>;
+ HandleMap mHandles;
+};
+
+void TimersListener::scheduleAfter(const LLSD& params)
+{
+ LLSD::Real after{ params["after"] };
+ if (after < MINTIMER)
{
+ sendReply(llsd::map("error", stringize("after must be at least ", MINTIMER)), params);
+ return;
}
-private:
- BOOL tick()
+ mHandles.emplace(
+ params["reqid"],
+ Timers::instance().scheduleAfter(
+ [this, params]
+ {
+ // we don't need any content save for the "reqid"
+ sendReply({}, params);
+ // ditch mHandles entry
+ mHandles.erase(params["reqid"]);
+ },
+ after));
+}
+
+void TimersListener::scheduleEvery(const LLSD& params)
+{
+ LLSD::Real every{ params["every"] };
+ if (every < MINTIMER)
{
- mCallable();
- return TRUE;
+ sendReply(llsd::map("error", stringize("every must be at least ", MINTIMER)), params);
+ return;
}
- nullary_func_t mCallable;
-};
+ mHandles.emplace(
+ params["reqid"],
+ Timers::instance().scheduleEvery(
+ [params, i=0]() mutable
+ {
+ // we don't need any content save for the "reqid"
+ sendReply(llsd::map("i", i++), params);
+ // we can't use a handshake -- always keep the ball rolling
+ return false;
+ },
+ every));
+}
-// Call a given callable once after specified interval.
-void doAfterInterval(nullary_func_t callable, F32 seconds)
+LLSD TimersListener::cancel(const LLSD& params)
{
- new NullaryFuncEventTimer(callable, seconds);
+ auto found{ mHandles.find(params["id"]) };
+ bool ok = false;
+ if (found != mHandles.end())
+ {
+ ok = true;
+ Timers::instance().cancel(found->second);
+ mHandles.erase(found);
+ }
+ return llsd::map("ok", ok);
}
-class BoolFuncEventTimer: public LLEventTimer
+LLSD TimersListener::isRunning(const LLSD& params)
{
-public:
- BoolFuncEventTimer(bool_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
+ auto found{ mHandles.find(params["id"]) };
+ bool running = false;
+ if (found != mHandles.end())
{
+ running = Timers::instance().isRunning(found->second);
}
-private:
- BOOL tick()
+ return llsd::map("running", running);
+}
+
+LLSD TimersListener::timeUntilCall(const LLSD& params)
+{
+ auto found{ mHandles.find(params["id"]) };
+ bool ok = false;
+ LLSD::Real remaining = 0;
+ if (found != mHandles.end())
{
- return mCallable();
+ ok = true;
+ remaining = Timers::instance().timeUntilCall(found->second);
}
+ return llsd::map("ok", ok, "remaining", remaining);
+}
- bool_func_t mCallable;
+class TimersRegistrar: public LazyEventAPI<TimersListener>
+{
+ using super = LazyEventAPI<TimersListener>;
+ using super::listener;
+
+public:
+ TimersRegistrar():
+ super("Timers", "Provide access to viewer timer functionality.")
+ {
+ add("scheduleAfter",
+R"-(Create a timer with ID "reqid". Post response after "after" seconds.)-",
+ &listener::scheduleAfter,
+ llsd::map("reqid", LLSD::Integer(), "after", LLSD::Real()));
+ add("scheduleEvery",
+R"-(Create a timer with ID "reqid". Post response every "every" seconds
+until cancel().)-",
+ &listener::scheduleEvery,
+ llsd::map("reqid", LLSD::Integer(), "every", LLSD::Real()));
+ add("cancel",
+R"-(Cancel the timer with ID "id". Respond "ok"=true if "id" identifies
+a live timer.)-",
+ &listener::cancel,
+ llsd::map("reqid", LLSD::Integer(), "id", LLSD::Integer()));
+ add("isRunning",
+R"-(Query the timer with ID "id": respond "running"=true if "id" identifies
+a live timer.)-",
+ &listener::isRunning,
+ llsd::map("reqid", LLSD::Integer(), "id", LLSD::Integer()));
+ add("timeUntilCall",
+R"-(Query the timer with ID "id": if "id" identifies a live timer, respond
+"ok"=true, "remaining"=seconds with the time left before timer expiry;
+otherwise "ok"=false, "remaining"=0.)-",
+ &listener::timeUntilCall,
+ llsd::map("reqid", LLSD::Integer()));
+ }
};
+static TimersRegistrar registrar;
-// Call a given callable every specified number of seconds, until it returns true.
-void doPeriodically(bool_func_t callable, F32 seconds)
-{
- new BoolFuncEventTimer(callable, seconds);
-}
+} // namespace LL
diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h
index d6c415f7c5..2fb27d5ea8 100644
--- a/indra/llcommon/llcallbacklist.h
+++ b/indra/llcommon/llcallbacklist.h
@@ -1,25 +1,25 @@
-/**
+/**
* @file llcallbacklist.h
* @brief A simple list of callback functions to call.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -27,53 +27,274 @@
#ifndef LL_LLCALLBACKLIST_H
#define LL_LLCALLBACKLIST_H
+#include "lldate.h"
+#include "llsingleton.h"
#include "llstl.h"
-#include <boost/function.hpp>
-#include <list>
+#include <boost/container_hash/hash.hpp>
+#include <boost/heap/fibonacci_heap.hpp>
+#include <boost/signals2.hpp>
+#include <functional>
+#include <unordered_map>
-class LLCallbackList
+/*****************************************************************************
+* LLCallbackList: callbacks every idle tick (every callFunctions() call)
+*****************************************************************************/
+class LLCallbackList: public LLSingleton<LLCallbackList>
{
+ LLSINGLETON(LLCallbackList);
public:
- typedef void (*callback_t)(void*);
+ typedef void (*callback_t)(void*);
- typedef std::pair< callback_t,void* > callback_pair_t;
- // NOTE: It is confirmed that we DEPEND on the order provided by using a list :(
- //
- typedef std::list< callback_pair_t > callback_list_t;
+ typedef boost::signals2::signal<void()> callback_list_t;
+ typedef callback_list_t::slot_type callable_t;
+ typedef boost::signals2::connection handle_t;
+ typedef boost::signals2::scoped_connection temp_handle_t;
+ typedef std::function<bool ()> bool_func_t;
- LLCallbackList();
- ~LLCallbackList();
+ ~LLCallbackList();
- void addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data)
- bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair
- bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found
- void callFunctions(); // calls all functions
- void deleteAllFunctions();
+ handle_t addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data)
+ handle_t addFunction( const callable_t& func );
+ bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair
+ bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found
+ void deleteFunction( const handle_t& handle );
+ void callFunctions(); // calls all functions
+ void deleteAllFunctions();
- static void test();
+ handle_t doOnIdleOneTime( const callable_t& func );
+ handle_t doOnIdleRepeating( const bool_func_t& func );
+ bool isRunning(const handle_t& handle) const { return handle.connected(); };
-protected:
+ static void test();
- inline callback_list_t::iterator find(callback_t func, void *data);
+protected:
+ callback_list_t mCallbackList;
- callback_list_t mCallbackList;
+ // "Additional specializations for std::pair and the standard container
+ // types, as well as utility functions to compose hashes are available in
+ // boost::hash."
+ // https://en.cppreference.com/w/cpp/utility/hash
+ typedef std::pair< callback_t,void* > callback_pair_t;
+ typedef std::unordered_map<callback_pair_t, handle_t,
+ boost::hash<callback_pair_t>> lookup_table;
+ lookup_table mLookup;
};
-typedef boost::function<void ()> nullary_func_t;
-typedef boost::function<bool ()> bool_func_t;
+/*-------------------- legacy names in global namespace --------------------*/
+#define gIdleCallbacks (LLCallbackList::instance())
+
+using nullary_func_t = LLCallbackList::callable_t;
+using bool_func_t = LLCallbackList::bool_func_t;
// Call a given callable once in idle loop.
-void doOnIdleOneTime(nullary_func_t callable);
+inline
+LLCallbackList::handle_t doOnIdleOneTime(nullary_func_t callable)
+{
+ return gIdleCallbacks.doOnIdleOneTime(callable);
+}
// Repeatedly call a callable in idle loop until it returns true.
-void doOnIdleRepeating(bool_func_t callable);
+inline
+LLCallbackList::handle_t doOnIdleRepeating(bool_func_t callable)
+{
+ return gIdleCallbacks.doOnIdleRepeating(callable);
+}
+
+/*****************************************************************************
+* LL::Timers: callbacks at some future time
+*****************************************************************************/
+namespace LL
+{
+
+class Timers: public LLSingleton<Timers>
+{
+ LLSINGLETON(Timers);
+
+ using token_t = U32;
+
+ // Define a struct for our priority queue entries, instead of using
+ // a tuple, because we need to define the comparison operator.
+ struct func_at
+ {
+ // callback to run when this timer fires
+ bool_func_t mFunc;
+ // key to look up metadata in mHandles
+ token_t mToken;
+ // time at which this timer is supposed to fire
+ LLDate::timestamp mTime;
+
+ func_at(const bool_func_t& func, token_t token, LLDate::timestamp tm):
+ mFunc(func),
+ mToken(token),
+ mTime(tm)
+ {}
+
+ friend bool operator<(const func_at& lhs, const func_at& rhs)
+ {
+ // use greater-than because we want fibonacci_heap to select the
+ // EARLIEST time as the top()
+ return lhs.mTime > rhs.mTime;
+ }
+ };
+
+ // Accept default stable<false>: when two funcs have the same timestamp,
+ // we don't care in what order they're called.
+ // Specify constant_time_size<false>: we don't need to optimize the size()
+ // method, iow we don't need to store and maintain a count of entries.
+ typedef boost::heap::fibonacci_heap<func_at, boost::heap::constant_time_size<false>>
+ queue_t;
+
+public:
+ // If tasks that come ready during a given tick() take longer than this,
+ // defer any subsequent ready tasks to a future tick() call.
+ static constexpr F32 TIMESLICE{ 0.005f };
+
+ class handle_t
+ {
+ private:
+ friend class Timers;
+ token_t token;
+ public:
+ handle_t(token_t token=0): token(token) {}
+ bool operator==(const handle_t& rhs) const { return this->token == rhs.token; }
+ explicit operator bool() const { return bool(token); }
+ bool operator!() const { return ! bool(*this); }
+ };
+ // Call a given callable once at specified timestamp.
+ handle_t scheduleAt(nullary_func_t callable, LLDate::timestamp time);
+
+ // Call a given callable once after specified interval.
+ handle_t scheduleAfter(nullary_func_t callable, F32 seconds);
+
+ // Call a given callable every specified number of seconds, until it returns true.
+ handle_t scheduleEvery(bool_func_t callable, F32 seconds);
+
+ // test whether specified handle is still live
+ bool isRunning(handle_t timer) const;
+ // check remaining time
+ F32 timeUntilCall(handle_t timer) const;
+
+ // Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleEvery().
+ // Return true if and only if the handle corresponds to a live timer.
+ bool cancel(const handle_t& timer);
+ // If we're canceling a non-const handle_t, also clear it so we need not
+ // cancel again.
+ bool cancel(handle_t& timer);
+
+ // Store a handle_t returned by scheduleAt(), scheduleAfter() or
+ // scheduleEvery() in a temp_handle_t to cancel() automatically on
+ // destruction of the temp_handle_t.
+ class temp_handle_t
+ {
+ public:
+ temp_handle_t() = default;
+ temp_handle_t(const handle_t& hdl): mHandle(hdl) {}
+ temp_handle_t(const temp_handle_t&) = delete;
+ temp_handle_t(temp_handle_t&&) = default;
+ temp_handle_t& operator=(const handle_t& hdl)
+ {
+ // initializing a new temp_handle_t, then swapping it into *this,
+ // takes care of destroying any previous mHandle
+ temp_handle_t replacement(hdl);
+ swap(replacement);
+ return *this;
+ }
+ temp_handle_t& operator=(const temp_handle_t&) = delete;
+ temp_handle_t& operator=(temp_handle_t&&) = default;
+ ~temp_handle_t()
+ {
+ cancel();
+ }
+
+ // temp_handle_t should be usable wherever handle_t is
+ operator handle_t() const { return mHandle; }
+ // If we're dealing with a non-const temp_handle_t, pass a reference
+ // to our handle_t member (e.g. to Timers::cancel()).
+ operator handle_t&() { return mHandle; }
+
+ // For those in the know, provide a cancel() method of our own that
+ // avoids Timers::instance() lookup when mHandle isn't live.
+ bool cancel()
+ {
+ if (! mHandle)
+ {
+ return false;
+ }
+ else
+ {
+ return Timers::instance().cancel(mHandle);
+ }
+ }
+
+ void swap(temp_handle_t& other) noexcept
+ {
+ std::swap(this->mHandle, other.mHandle);
+ }
+
+ private:
+ handle_t mHandle;
+ };
+
+private:
+ handle_t scheduleAtEvery(bool_func_t callable, LLDate::timestamp time, F32 interval);
+ LLDate::timestamp now() const { return LLDate::now().secondsSinceEpoch(); }
+ // wrap a nullary_func_t with a bool_func_t that will only execute once
+ bool_func_t once(nullary_func_t callable)
+ {
+ return [callable]
+ {
+ callable();
+ return true;
+ };
+ }
+ bool tick();
+
+ // NOTE: We don't lock our data members because it doesn't make sense to
+ // register cross-thread callbacks. If we start wanting to use Timers on
+ // threads other than the main thread, it would make more sense to make
+ // our data members thread_local than to lock them.
+
+ // the heap aka priority queue
+ queue_t mQueue;
+
+ // metadata about a given task
+ struct Metadata
+ {
+ // handle to mQueue entry
+ queue_t::handle_type mHandle;
+ // time at which this timer is supposed to fire
+ LLDate::timestamp mTime;
+ // interval at which this timer is supposed to fire repeatedly
+ F32 mInterval{ 0 };
+ // mFunc is currently running: don't delete this entry
+ bool mRunning{ false };
+ // cancel() was called while mFunc was running: deferred cancel
+ bool mCancel{ false };
+ };
+
+ using MetaMap = std::unordered_map<token_t, Metadata>;
+ MetaMap mMeta;
+ token_t mToken{ 0 };
+ // While mQueue is non-empty, register for regular callbacks.
+ LLCallbackList::temp_handle_t mLive;
+};
+
+} // namespace LL
+
+/*-------------------- legacy names in global namespace --------------------*/
// Call a given callable once after specified interval.
-void doAfterInterval(nullary_func_t callable, F32 seconds);
+inline
+LL::Timers::handle_t doAfterInterval(nullary_func_t callable, F32 seconds)
+{
+ return LL::Timers::instance().scheduleAfter(callable, seconds);
+}
// Call a given callable every specified number of seconds, until it returns true.
-void doPeriodically(bool_func_t callable, F32 seconds);
-
-extern LLCallbackList gIdleCallbacks;
+inline
+LL::Timers::handle_t doPeriodically(bool_func_t callable, F32 seconds)
+{
+ return LL::Timers::instance().scheduleEvery(callable, seconds);
+}
#endif
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 20340397db..a6d7988256 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-06-03
* @brief Implementation for llcoros.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -51,18 +51,19 @@
#endif
// other Linden headers
#include "llapp.h"
-#include "lltimer.h"
-#include "llevents.h"
#include "llerror.h"
-#include "stringize.h"
+#include "llevents.h"
#include "llexception.h"
+#include "llsdutil.h"
+#include "lltimer.h"
+#include "stringize.h"
#if LL_WINDOWS
#include <excpt.h>
#endif
// static
-LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
+LLCoros::CoroData& LLCoros::get_CoroData(const std::string&)
{
CoroData* current{ nullptr };
// be careful about attempted accesses in the final throes of app shutdown
@@ -94,7 +95,7 @@ LLCoros::coro::id LLCoros::get_self()
//static
void LLCoros::set_consuming(bool consuming)
{
- CoroData& data(get_CoroData("set_consuming()"));
+ auto& data(get_CoroData("set_consuming()"));
// DO NOT call this on the main() coroutine.
llassert_always(! data.mName.empty());
data.mConsuming = consuming;
@@ -123,11 +124,20 @@ LLCoros::LLCoros():
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
// empirically this is insufficient.
- mStackSize(900*1024),
+ mStackSize(1024*1024),
// mCurrent does NOT own the current CoroData instance -- it simply
// points to it. So initialize it with a no-op deleter.
mCurrent{ [](CoroData*){} }
{
+ auto& llapp{ LLEventPumps::instance().obtain("LLApp") };
+ if (llapp.getListener("LLCoros") == LLBoundListener())
+ {
+ // chain our "LLCoros" pump onto "LLApp" pump: echo events posted to "LLApp"
+ mConn = llapp.listen(
+ "LLCoros",
+ [](const LLSD& event)
+ { return LLEventPumps::instance().obtain("LLCoros").post(event); });
+ }
}
LLCoros::~LLCoros()
@@ -177,26 +187,26 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
// Until we find an unused name, append a numeric suffix for uniqueness.
while (CoroData::getInstance(name))
{
- name = STRINGIZE(prefix << unique++);
+ name = stringize(prefix, unique++);
}
return name;
}
-/*==========================================================================*|
-bool LLCoros::kill(const std::string& name)
+bool LLCoros::killreq(const std::string& name)
{
- CoroMap::iterator found = mCoros.find(name);
- if (found == mCoros.end())
+ auto found = CoroData::getInstance(name);
+ if (! found)
{
return false;
}
- // Because this is a boost::ptr_map, erasing the map entry also destroys
- // the referenced heap object, in this case the boost::coroutine object,
- // which will terminate the coroutine.
- mCoros.erase(found);
+ // Next time the subject coroutine calls checkStop(), make it terminate.
+ found->mKilledBy = getName();
+ // But if it's waiting for something, notify anyone in a position to poke
+ // it.
+ LLEventPumps::instance().obtain("LLCoros").post(
+ llsd::map("status", "killreq", "coro", name));
return true;
}
-|*==========================================================================*/
//static
std::string LLCoros::getName()
@@ -207,7 +217,7 @@ std::string LLCoros::getName()
//static
std::string LLCoros::logname()
{
- LLCoros::CoroData& data(get_CoroData("logname()"));
+ auto& data(get_CoroData("logname()"));
return data.mName.empty()? data.getKey() : data.mName;
}
@@ -360,7 +370,7 @@ void LLCoros::toplevel(std::string name, callable_t callable)
// 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 " << name));
+ LOG_UNHANDLED_EXCEPTION(stringize("coroutine ", name));
}
catch (...)
{
@@ -373,15 +383,24 @@ void LLCoros::toplevel(std::string name, callable_t callable)
}
//static
-void LLCoros::checkStop()
+void LLCoros::checkStop(callable_t cleanup)
{
+ // don't replicate this 'if' test throughout the code below
+ if (! cleanup)
+ {
+ cleanup = {[](){}}; // hey, look, I'm coding in Haskell!
+ }
+
if (wasDeleted())
{
+ cleanup();
LLTHROW(Shutdown("LLCoros was deleted"));
}
- // do this AFTER the check above, because getName() depends on
- // get_CoroData(), which depends on the local_ptr in our instance().
- if (getName().empty())
+
+ // do this AFTER the check above, because get_CoroData() depends on the
+ // local_ptr in our instance().
+ auto& data(get_CoroData("checkStop()"));
+ if (data.mName.empty())
{
// Our Stop exception and its subclasses are intended to stop loitering
// coroutines. Don't throw it from the main coroutine.
@@ -389,19 +408,80 @@ void LLCoros::checkStop()
}
if (LLApp::isStopped())
{
+ cleanup();
LLTHROW(Stopped("viewer is stopped"));
}
if (! LLApp::isRunning())
{
+ cleanup();
LLTHROW(Stopping("viewer is stopping"));
}
+ if (! data.mKilledBy.empty())
+ {
+ // Someone wants to kill this coroutine
+ cleanup();
+ LLTHROW(Killed(stringize("coroutine ", data.mName, " killed by ", data.mKilledBy)));
+ }
+}
+
+LLBoundListener LLCoros::getStopListener(const std::string& caller, LLVoidListener cleanup)
+{
+ if (! cleanup)
+ return {};
+
+ // This overload only responds to viewer shutdown.
+ return LLEventPumps::instance().obtain("LLCoros")
+ .listen(
+ LLEventPump::inventName(caller),
+ [cleanup](const LLSD& event)
+ {
+ auto status{ event["status"].asString() };
+ if (status != "running" && status != "killreq")
+ {
+ cleanup(event);
+ }
+ return false;
+ });
+}
+
+LLBoundListener LLCoros::getStopListener(const std::string& caller,
+ const std::string& cnsmr,
+ LLVoidListener cleanup)
+{
+ if (! cleanup)
+ return {};
+
+ std::string consumer{cnsmr};
+ if (consumer.empty())
+ {
+ consumer = getName();
+ }
+
+ // This overload responds to viewer shutdown and to killreq(consumer).
+ return LLEventPumps::instance().obtain("LLCoros")
+ .listen(
+ LLEventPump::inventName(caller),
+ [consumer, cleanup](const LLSD& event)
+ {
+ auto status{ event["status"].asString() };
+ if (status == "killreq")
+ {
+ if (event["coro"].asString() == consumer)
+ {
+ cleanup(event);
+ }
+ }
+ else if (status != "running")
+ {
+ cleanup(event);
+ }
+ return false;
+ });
}
LLCoros::CoroData::CoroData(const std::string& name):
LLInstanceTracker<CoroData, std::string>(name),
mName(name),
- // don't consume events unless specifically directed
- mConsuming(false),
mCreationTime(LLTimer::getTotalSeconds())
{
}
@@ -414,7 +494,6 @@ LLCoros::CoroData::CoroData(int n):
// empty string as its visible name because some consumers test for that.
LLInstanceTracker<CoroData, std::string>("main" + stringize(n)),
mName(),
- mConsuming(false),
mCreationTime(LLTimer::getTotalSeconds())
{
}
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index 71c1c1c443..61c0fef1c3 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-06-02
* @brief Manage running boost::coroutine instances
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -29,17 +29,18 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
+#include "llevents.h"
#include "llexception.h"
+#include "llinstancetracker.h"
+#include "llsingleton.h"
+#include "mutex.h"
#include <boost/fiber/fss.hpp>
#include <boost/fiber/future/promise.hpp>
#include <boost/fiber/future/future.hpp>
-#include "mutex.h"
-#include "llsingleton.h"
-#include "llinstancetracker.h"
-#include <boost/function.hpp>
-#include <string>
#include <exception>
+#include <functional>
#include <queue>
+#include <string>
// e.g. #include LLCOROS_MUTEX_HEADER
#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp>
@@ -101,7 +102,7 @@ public:
/// stuck with the term "coroutine."
typedef boost::fibers::fiber coro;
/// Canonical callable type
- typedef boost::function<void()> callable_t;
+ typedef std::function<void()> callable_t;
/**
* Create and start running a new coroutine with specified name. The name
@@ -143,13 +144,13 @@ public:
std::string launch(const std::string& prefix, const callable_t& callable);
/**
- * Abort a running coroutine by name. Normally, when a coroutine either
+ * Ask the named coroutine to abort. Normally, when a coroutine either
* runs to completion or terminates with an exception, LLCoros quietly
* cleans it up. This is for use only when you must explicitly interrupt
* one prematurely. Returns @c true if the specified name was found and
* still running at the time.
*/
-// bool kill(const std::string& name);
+ bool killreq(const std::string& name);
/**
* From within a coroutine, look up the (tweaked) name string by which
@@ -158,7 +159,7 @@ public:
* LLCoros::launch()).
*/
static std::string getName();
-
+
/**
* rethrow() is called by the thread's main fiber to propagate an
* exception from any coroutine into the main fiber, where it can engage
@@ -251,15 +252,21 @@ public:
/// thrown by checkStop()
// It may sound ironic that Stop is derived from LLContinueError, but the
// point is that LLContinueError is the category of exception that should
- // not immediately crash the viewer. Stop and its subclasses are to notify
- // coroutines that the viewer intends to shut down. The expected response
- // is to terminate the coroutine, rather than abort the viewer.
+ // not immediately crash the viewer. Stop and its subclasses are to tell
+ // coroutines to terminate, e.g. because the viewer is shutting down. We
+ // do not want any such exception to crash the viewer.
struct Stop: public LLContinueError
{
Stop(const std::string& what): LLContinueError(what) {}
};
- /// early stages
+ /// someone wants to kill this specific coroutine
+ struct Killed: public Stop
+ {
+ Killed(const std::string& what): Stop(what) {}
+ };
+
+ /// early shutdown stages
struct Stopping: public Stop
{
Stopping(const std::string& what): Stop(what) {}
@@ -278,9 +285,31 @@ public:
};
/// Call this intermittently if there's a chance your coroutine might
- /// continue running into application shutdown. Throws Stop if LLCoros has
- /// been cleaned up.
- static void checkStop();
+ /// still be running at application shutdown. Throws one of the Stop
+ /// subclasses if the caller needs to terminate. Pass a cleanup function
+ /// if you need to execute that cleanup before terminating.
+ /// Of course, if your cleanup function throws, that will be the exception
+ /// propagated by checkStop().
+ static void checkStop(callable_t cleanup={});
+
+ /// Call getStopListener() at the source end of a queue, promise or other
+ /// resource on which coroutines will wait, so that shutdown can wake up
+ /// consuming coroutines. @a caller should distinguish who's calling. The
+ /// passed @a cleanup function must close the queue, break the promise or
+ /// otherwise cause waiting consumers to wake up in an abnormal way. It's
+ /// advisable to store the returned LLBoundListener in an
+ /// LLTempBoundListener, or otherwise arrange to disconnect it.
+ static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup);
+
+ /// This getStopListener() overload is like the two-argument one, for use
+ /// when we know the name of the only coroutine that will wait on the
+ /// resource in question. Pass @a consumer as the empty string if the
+ /// consumer coroutine is the same as the calling coroutine. Unlike the
+ /// two-argument getStopListener(), this one also responds to
+ /// killreq(target).
+ static LLBoundListener getStopListener(const std::string& caller,
+ const std::string& consumer,
+ LLVoidListener cleanup);
/**
* Aliases for promise and future. An older underlying future implementation
@@ -312,6 +341,8 @@ private:
static CoroData& get_CoroData(const std::string& caller);
void saveException(const std::string& name, std::exception_ptr exc);
+ LLTempBoundListener mConn;
+
struct ExceptionData
{
ExceptionData(const std::string& nm, std::exception_ptr exc):
@@ -335,8 +366,10 @@ private:
// tweaked name of the current coroutine
const std::string mName;
- // set_consuming() state
- bool mConsuming;
+ // set_consuming() state -- don't consume events unless specifically directed
+ bool mConsuming{ false };
+ // killed by which coroutine
+ std::string mKilledBy;
// setStatus() state
std::string mStatus;
F64 mCreationTime; // since epoch
diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp
index c63c7012d1..6c23444820 100644
--- a/indra/llcommon/lldate.cpp
+++ b/indra/llcommon/lldate.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* @file lldate.cpp
* @author Phoenix
* @date 2006-02-05
@@ -7,21 +7,21 @@
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -41,39 +41,39 @@
#include "llstring.h"
#include "llfasttimer.h"
-static const F64 DATE_EPOCH = 0.0;
+static const LLDate::timestamp DATE_EPOCH = 0.0;
-static const F64 LL_APR_USEC_PER_SEC = 1000000.0;
- // should be APR_USEC_PER_SEC, but that relies on INT64_C which
- // isn't defined in glib under our build set up for some reason
+static const LLDate::timestamp LL_APR_USEC_PER_SEC = 1000000.0;
+ // should be APR_USEC_PER_SEC, but that relies on INT64_C which
+ // isn't defined in glib under our build set up for some reason
LLDate::LLDate() : mSecondsSinceEpoch(DATE_EPOCH)
{}
LLDate::LLDate(const LLDate& date) :
- mSecondsSinceEpoch(date.mSecondsSinceEpoch)
+ mSecondsSinceEpoch(date.mSecondsSinceEpoch)
{}
LLDate::LLDate(F64SecondsImplicit seconds_since_epoch) :
- mSecondsSinceEpoch(seconds_since_epoch.value())
+ mSecondsSinceEpoch(seconds_since_epoch.value())
{}
LLDate::LLDate(const std::string& iso8601_date)
{
- if(!fromString(iso8601_date))
- {
- LL_WARNS() << "date " << iso8601_date << " failed to parse; "
- << "ZEROING IT OUT" << LL_ENDL;
- mSecondsSinceEpoch = DATE_EPOCH;
- }
+ if(!fromString(iso8601_date))
+ {
+ LL_WARNS() << "date " << iso8601_date << " failed to parse; "
+ << "ZEROING IT OUT" << LL_ENDL;
+ mSecondsSinceEpoch = DATE_EPOCH;
+ }
}
std::string LLDate::asString() const
{
- std::ostringstream stream;
- toStream(stream);
- return stream.str();
+ std::ostringstream stream;
+ toStream(stream);
+ return stream.str();
}
//@ brief Converts time in seconds since EPOCH
@@ -83,236 +83,236 @@ std::string LLDate::asString() const
// is one of the standards used and the prefered format
std::string LLDate::asRFC1123() const
{
- return toHTTPDateString (std::string ("%A, %d %b %Y %H:%M:%S GMT"));
+ return toHTTPDateString (std::string ("%A, %d %b %Y %H:%M:%S GMT"));
}
std::string LLDate::toHTTPDateString (std::string fmt) const
{
LL_PROFILE_ZONE_SCOPED;
-
- time_t locSeconds = (time_t) mSecondsSinceEpoch;
- struct tm * gmt = gmtime (&locSeconds);
- return toHTTPDateString(gmt, fmt);
+
+ time_t locSeconds = (time_t) mSecondsSinceEpoch;
+ struct tm * gmt = gmtime (&locSeconds);
+ return toHTTPDateString(gmt, fmt);
}
std::string LLDate::toHTTPDateString (tm * gmt, std::string fmt)
{
LL_PROFILE_ZONE_SCOPED;
- // avoid calling setlocale() unnecessarily - it's expensive.
- static std::string prev_locale = "";
- std::string this_locale = LLStringUtil::getLocale();
- if (this_locale != prev_locale)
- {
- setlocale(LC_TIME, this_locale.c_str());
- prev_locale = this_locale;
- }
-
- // use strftime() as it appears to be faster than std::time_put
- char buffer[128];
- strftime(buffer, 128, fmt.c_str(), gmt);
- std::string res(buffer);
+ // avoid calling setlocale() unnecessarily - it's expensive.
+ static std::string prev_locale = "";
+ std::string this_locale = LLStringUtil::getLocale();
+ if (this_locale != prev_locale)
+ {
+ setlocale(LC_TIME, this_locale.c_str());
+ prev_locale = this_locale;
+ }
+
+ // use strftime() as it appears to be faster than std::time_put
+ char buffer[128];
+ strftime(buffer, 128, fmt.c_str(), gmt);
+ std::string res(buffer);
#if LL_WINDOWS
- // Convert from locale-dependant charset to UTF-8 (EXT-8524).
- res = ll_convert_string_to_utf8_string(res);
+ // Convert from locale-dependant charset to UTF-8 (EXT-8524).
+ res = ll_convert_string_to_utf8_string(res);
#endif
- return res;
+ return res;
}
void LLDate::toStream(std::ostream& s) const
{
- apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC);
-
- apr_time_exp_t exp_time;
- if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS)
- {
- s << "1970-01-01T00:00:00Z";
- return;
- }
-
- s << std::dec << std::setfill('0');
+ apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC);
+
+ apr_time_exp_t exp_time;
+ if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS)
+ {
+ s << "1970-01-01T00:00:00Z";
+ return;
+ }
+
+ s << std::dec << std::setfill('0');
#if( LL_WINDOWS || __GNUC__ > 2)
- s << std::right;
+ s << std::right;
#else
- s.setf(ios::right);
+ s.setf(ios::right);
#endif
- s << std::setw(4) << (exp_time.tm_year + 1900)
- << '-' << std::setw(2) << (exp_time.tm_mon + 1)
- << '-' << std::setw(2) << (exp_time.tm_mday)
- << 'T' << std::setw(2) << (exp_time.tm_hour)
- << ':' << std::setw(2) << (exp_time.tm_min)
- << ':' << std::setw(2) << (exp_time.tm_sec);
- if (exp_time.tm_usec > 0)
- {
- s << '.' << std::setw(2)
- << (int)(exp_time.tm_usec / (LL_APR_USEC_PER_SEC / 100));
- }
- s << 'Z'
- << std::setfill(' ');
+ s << std::setw(4) << (exp_time.tm_year + 1900)
+ << '-' << std::setw(2) << (exp_time.tm_mon + 1)
+ << '-' << std::setw(2) << (exp_time.tm_mday)
+ << 'T' << std::setw(2) << (exp_time.tm_hour)
+ << ':' << std::setw(2) << (exp_time.tm_min)
+ << ':' << std::setw(2) << (exp_time.tm_sec);
+ if (exp_time.tm_usec > 0)
+ {
+ s << '.' << std::setw(2)
+ << (int)(exp_time.tm_usec / (LL_APR_USEC_PER_SEC / 100));
+ }
+ s << 'Z'
+ << std::setfill(' ');
}
bool LLDate::split(S32 *year, S32 *month, S32 *day, S32 *hour, S32 *min, S32 *sec) const
{
- apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC);
-
- apr_time_exp_t exp_time;
- if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS)
- {
- return false;
- }
+ apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC);
+
+ apr_time_exp_t exp_time;
+ if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS)
+ {
+ return false;
+ }
- if (year)
- *year = exp_time.tm_year + 1900;
+ if (year)
+ *year = exp_time.tm_year + 1900;
- if (month)
- *month = exp_time.tm_mon + 1;
+ if (month)
+ *month = exp_time.tm_mon + 1;
- if (day)
- *day = exp_time.tm_mday;
+ if (day)
+ *day = exp_time.tm_mday;
- if (hour)
- *hour = exp_time.tm_hour;
+ if (hour)
+ *hour = exp_time.tm_hour;
- if (min)
- *min = exp_time.tm_min;
+ if (min)
+ *min = exp_time.tm_min;
- if (sec)
- *sec = exp_time.tm_sec;
+ if (sec)
+ *sec = exp_time.tm_sec;
- return true;
+ return true;
}
bool LLDate::fromString(const std::string& iso8601_date)
{
- std::istringstream stream(iso8601_date);
- return fromStream(stream);
+ std::istringstream stream(iso8601_date);
+ return fromStream(stream);
}
bool LLDate::fromStream(std::istream& s)
{
- struct apr_time_exp_t exp_time;
- apr_int32_t tm_part;
- int c;
-
- s >> tm_part;
- exp_time.tm_year = tm_part - 1900;
- c = s.get(); // skip the hypen
- if (c != '-') { return false; }
- s >> tm_part;
- exp_time.tm_mon = tm_part - 1;
- c = s.get(); // skip the hypen
- if (c != '-') { return false; }
- s >> tm_part;
- exp_time.tm_mday = tm_part;
-
- c = s.get(); // skip the T
- if (c != 'T') { return false; }
-
- s >> tm_part;
- exp_time.tm_hour = tm_part;
- c = s.get(); // skip the :
- if (c != ':') { return false; }
- s >> tm_part;
- exp_time.tm_min = tm_part;
- c = s.get(); // skip the :
- if (c != ':') { return false; }
- s >> tm_part;
- exp_time.tm_sec = tm_part;
-
- // zero out the unused fields
- exp_time.tm_usec = 0;
- exp_time.tm_wday = 0;
- exp_time.tm_yday = 0;
- exp_time.tm_isdst = 0;
- exp_time.tm_gmtoff = 0;
-
- // generate a time_t from that
- apr_time_t time;
- if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS)
- {
- return false;
- }
-
- F64 seconds_since_epoch = time / LL_APR_USEC_PER_SEC;
-
- // check for fractional
- c = s.peek();
- if(c == '.')
- {
- F64 fractional = 0.0;
- s >> fractional;
- seconds_since_epoch += fractional;
- }
-
- c = s.peek(); // check for offset
- if (c == '+' || c == '-')
- {
- S32 offset_sign = (c == '+') ? 1 : -1;
- S32 offset_hours = 0;
- S32 offset_minutes = 0;
- S32 offset_in_seconds = 0;
-
- s >> offset_hours;
-
- c = s.get(); // skip the colon a get the minutes if there are any
- if (c == ':')
- {
- s >> offset_minutes;
- }
-
- offset_in_seconds = (offset_hours * 60 + offset_sign * offset_minutes) * 60;
- seconds_since_epoch -= offset_in_seconds;
- }
- else if (c != 'Z') { return false; } // skip the Z
-
- mSecondsSinceEpoch = seconds_since_epoch;
- return true;
+ struct apr_time_exp_t exp_time;
+ apr_int32_t tm_part;
+ int c;
+
+ s >> tm_part;
+ exp_time.tm_year = tm_part - 1900;
+ c = s.get(); // skip the hypen
+ if (c != '-') { return false; }
+ s >> tm_part;
+ exp_time.tm_mon = tm_part - 1;
+ c = s.get(); // skip the hypen
+ if (c != '-') { return false; }
+ s >> tm_part;
+ exp_time.tm_mday = tm_part;
+
+ c = s.get(); // skip the T
+ if (c != 'T') { return false; }
+
+ s >> tm_part;
+ exp_time.tm_hour = tm_part;
+ c = s.get(); // skip the :
+ if (c != ':') { return false; }
+ s >> tm_part;
+ exp_time.tm_min = tm_part;
+ c = s.get(); // skip the :
+ if (c != ':') { return false; }
+ s >> tm_part;
+ exp_time.tm_sec = tm_part;
+
+ // zero out the unused fields
+ exp_time.tm_usec = 0;
+ exp_time.tm_wday = 0;
+ exp_time.tm_yday = 0;
+ exp_time.tm_isdst = 0;
+ exp_time.tm_gmtoff = 0;
+
+ // generate a time_t from that
+ apr_time_t time;
+ if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS)
+ {
+ return false;
+ }
+
+ timestamp seconds_since_epoch = time / LL_APR_USEC_PER_SEC;
+
+ // check for fractional
+ c = s.peek();
+ if(c == '.')
+ {
+ timestamp fractional = 0.0;
+ s >> fractional;
+ seconds_since_epoch += fractional;
+ }
+
+ c = s.peek(); // check for offset
+ if (c == '+' || c == '-')
+ {
+ S32 offset_sign = (c == '+') ? 1 : -1;
+ S32 offset_hours = 0;
+ S32 offset_minutes = 0;
+ S32 offset_in_seconds = 0;
+
+ s >> offset_hours;
+
+ c = s.get(); // skip the colon a get the minutes if there are any
+ if (c == ':')
+ {
+ s >> offset_minutes;
+ }
+
+ offset_in_seconds = (offset_hours * 60 + offset_sign * offset_minutes) * 60;
+ seconds_since_epoch -= offset_in_seconds;
+ }
+ else if (c != 'Z') { return false; } // skip the Z
+
+ mSecondsSinceEpoch = seconds_since_epoch;
+ return true;
}
bool LLDate::fromYMDHMS(S32 year, S32 month, S32 day, S32 hour, S32 min, S32 sec)
{
- struct apr_time_exp_t exp_time;
-
- exp_time.tm_year = year - 1900;
- exp_time.tm_mon = month - 1;
- exp_time.tm_mday = day;
- exp_time.tm_hour = hour;
- exp_time.tm_min = min;
- exp_time.tm_sec = sec;
-
- // zero out the unused fields
- exp_time.tm_usec = 0;
- exp_time.tm_wday = 0;
- exp_time.tm_yday = 0;
- exp_time.tm_isdst = 0;
- exp_time.tm_gmtoff = 0;
-
- // generate a time_t from that
- apr_time_t time;
- if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS)
- {
- return false;
- }
-
- mSecondsSinceEpoch = time / LL_APR_USEC_PER_SEC;
-
- return true;
+ struct apr_time_exp_t exp_time;
+
+ exp_time.tm_year = year - 1900;
+ exp_time.tm_mon = month - 1;
+ exp_time.tm_mday = day;
+ exp_time.tm_hour = hour;
+ exp_time.tm_min = min;
+ exp_time.tm_sec = sec;
+
+ // zero out the unused fields
+ exp_time.tm_usec = 0;
+ exp_time.tm_wday = 0;
+ exp_time.tm_yday = 0;
+ exp_time.tm_isdst = 0;
+ exp_time.tm_gmtoff = 0;
+
+ // generate a time_t from that
+ apr_time_t time;
+ if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS)
+ {
+ return false;
+ }
+
+ mSecondsSinceEpoch = time / LL_APR_USEC_PER_SEC;
+
+ return true;
}
-F64 LLDate::secondsSinceEpoch() const
+LLDate::timestamp LLDate::secondsSinceEpoch() const
{
- return mSecondsSinceEpoch;
+ return mSecondsSinceEpoch;
}
-void LLDate::secondsSinceEpoch(F64 seconds)
+void LLDate::secondsSinceEpoch(timestamp seconds)
{
- mSecondsSinceEpoch = seconds;
+ mSecondsSinceEpoch = seconds;
}
/* static */ LLDate LLDate::now()
{
- // time() returns seconds, we want fractions of a second, which LLTimer provides --RN
- return LLDate(LLTimer::getTotalSeconds());
+ // time() returns seconds, we want fractions of a second, which LLTimer provides --RN
+ return LLDate(LLTimer::getTotalSeconds());
}
bool LLDate::operator<(const LLDate& rhs) const
@@ -322,13 +322,13 @@ bool LLDate::operator<(const LLDate& rhs) const
std::ostream& operator<<(std::ostream& s, const LLDate& date)
{
- date.toStream(s);
- return s;
+ date.toStream(s);
+ return s;
}
std::istream& operator>>(std::istream& s, LLDate& date)
{
- date.fromStream(s);
- return s;
+ date.fromStream(s);
+ return s;
}
diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h
index 81f2dd0d1c..c3d0cb97f3 100644
--- a/indra/llcommon/lldate.h
+++ b/indra/llcommon/lldate.h
@@ -1,4 +1,4 @@
-/**
+/**
* @file lldate.h
* @author Phoenix
* @date 2006-02-05
@@ -7,21 +7,21 @@
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -35,7 +35,7 @@
#include "stdtypes.h"
#include "llunits.h"
-/**
+/**
* @class LLDate
* @brief This class represents a particular point in time in UTC.
*
@@ -44,110 +44,112 @@
class LL_COMMON_API LLDate
{
public:
- /**
- * @brief Construct a date equal to epoch.
- */
- LLDate();
-
- /**
- * @brief Construct a date equal to the source date.
- */
- LLDate(const LLDate& date);
-
- /**
- * @brief Construct a date from a seconds since epoch value.
- *
- * @param seconds_since_epoch The number of seconds since UTC epoch.
- */
- LLDate(F64SecondsImplicit seconds_since_epoch);
-
- /**
- * @brief Construct a date from a string representation
- *
- * The date is constructed in the <code>fromString()</code>
- * method. See that method for details of supported formats.
- * If that method fails to parse the date, the date is set to epoch.
- * @param iso8601_date An iso-8601 compatible representation of the date.
- */
- LLDate(const std::string& iso8601_date);
-
- /**
- * @brief Return the date as in ISO-8601 string.
- *
- * @return A string representation of the date.
- */
- std::string asString() const;
- std::string asRFC1123() const;
- void toStream(std::ostream&) const;
- bool split(S32 *year, S32 *month = NULL, S32 *day = NULL, S32 *hour = NULL, S32 *min = NULL, S32 *sec = NULL) const;
- std::string toHTTPDateString (std::string fmt) const;
- static std::string toHTTPDateString (tm * gmt, std::string fmt);
- /**
- * @brief Set the date from an ISO-8601 string.
- *
- * The parser only supports strings conforming to
- * YYYYF-MM-DDTHH:MM:SS.FFZ where Y is year, M is month, D is day,
- * H is hour, M is minute, S is second, F is sub-second, and all
- * other characters are literal.
- * If this method fails to parse the date, the previous date is
- * retained.
- * @param iso8601_date An iso-8601 compatible representation of the date.
- * @return Returns true if the string was successfully parsed.
- */
- bool fromString(const std::string& iso8601_date);
- bool fromStream(std::istream&);
- bool fromYMDHMS(S32 year, S32 month = 1, S32 day = 0, S32 hour = 0, S32 min = 0, S32 sec = 0);
-
- /**
- * @brief Return the date in seconds since epoch.
- *
- * @return The number of seconds since epoch UTC.
- */
- F64 secondsSinceEpoch() const;
-
- /**
- * @brief Set the date in seconds since epoch.
- *
- * @param seconds The number of seconds since epoch UTC.
- */
- void secondsSinceEpoch(F64 seconds);
-
+ using timestamp = F64;
+
+ /**
+ * @brief Construct a date equal to epoch.
+ */
+ LLDate();
+
+ /**
+ * @brief Construct a date equal to the source date.
+ */
+ LLDate(const LLDate& date);
+
+ /**
+ * @brief Construct a date from a seconds since epoch value.
+ *
+ * @param seconds_since_epoch The number of seconds since UTC epoch.
+ */
+ LLDate(F64SecondsImplicit seconds_since_epoch);
+
+ /**
+ * @brief Construct a date from a string representation
+ *
+ * The date is constructed in the <code>fromString()</code>
+ * method. See that method for details of supported formats.
+ * If that method fails to parse the date, the date is set to epoch.
+ * @param iso8601_date An iso-8601 compatible representation of the date.
+ */
+ LLDate(const std::string& iso8601_date);
+
+ /**
+ * @brief Return the date as in ISO-8601 string.
+ *
+ * @return A string representation of the date.
+ */
+ std::string asString() const;
+ std::string asRFC1123() const;
+ void toStream(std::ostream&) const;
+ bool split(S32 *year, S32 *month = NULL, S32 *day = NULL, S32 *hour = NULL, S32 *min = NULL, S32 *sec = NULL) const;
+ std::string toHTTPDateString (std::string fmt) const;
+ static std::string toHTTPDateString (tm * gmt, std::string fmt);
+ /**
+ * @brief Set the date from an ISO-8601 string.
+ *
+ * The parser only supports strings conforming to
+ * YYYYF-MM-DDTHH:MM:SS.FFZ where Y is year, M is month, D is day,
+ * H is hour, M is minute, S is second, F is sub-second, and all
+ * other characters are literal.
+ * If this method fails to parse the date, the previous date is
+ * retained.
+ * @param iso8601_date An iso-8601 compatible representation of the date.
+ * @return Returns true if the string was successfully parsed.
+ */
+ bool fromString(const std::string& iso8601_date);
+ bool fromStream(std::istream&);
+ bool fromYMDHMS(S32 year, S32 month = 1, S32 day = 0, S32 hour = 0, S32 min = 0, S32 sec = 0);
+
+ /**
+ * @brief Return the date in seconds since epoch.
+ *
+ * @return The number of seconds since epoch UTC.
+ */
+ timestamp secondsSinceEpoch() const;
+
+ /**
+ * @brief Set the date in seconds since epoch.
+ *
+ * @param seconds The number of seconds since epoch UTC.
+ */
+ void secondsSinceEpoch(timestamp seconds);
+
/**
* @brief Create an LLDate object set to the current time.
- *
- * @return The number of seconds since epoch UTC.
- */
+ *
+ * @return The number of seconds since epoch UTC.
+ */
static LLDate now();
- /**
- * @brief Compare dates using operator< so we can order them using STL.
- *
- * @param rhs -- the right hand side of the comparison operator
- */
- bool operator<(const LLDate& rhs) const;
-
- /**
- * @brief Remaining comparison operators in terms of operator<
+ /**
+ * @brief Compare dates using operator< so we can order them using STL.
+ *
+ * @param rhs -- the right hand side of the comparison operator
+ */
+ bool operator<(const LLDate& rhs) const;
+
+ /**
+ * @brief Remaining comparison operators in terms of operator<
* This conforms to the expectation of STL.
- *
- * @param rhs -- the right hand side of the comparison operator
- */
+ *
+ * @param rhs -- the right hand side of the comparison operator
+ */
bool operator>(const LLDate& rhs) const { return rhs < *this; }
bool operator<=(const LLDate& rhs) const { return !(rhs < *this); }
bool operator>=(const LLDate& rhs) const { return !(*this < rhs); }
bool operator!=(const LLDate& rhs) const { return (*this < rhs) || (rhs < *this); }
bool operator==(const LLDate& rhs) const { return !(*this != rhs); }
- /**
- * @brief Compare to epoch UTC.
- */
-
- bool isNull() const { return mSecondsSinceEpoch == 0.0; }
- bool notNull() const { return mSecondsSinceEpoch != 0.0; }
+ /**
+ * @brief Compare to epoch UTC.
+ */
+ bool isNull() const { return mSecondsSinceEpoch == 0.0; }
+ bool notNull() const { return mSecondsSinceEpoch != 0.0; }
+
private:
- F64 mSecondsSinceEpoch;
+ timestamp mSecondsSinceEpoch;
};
// Helper function to stream out a date
diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h
index 47b6fedc7d..e225119dfb 100644
--- a/indra/llcommon/lldependencies.h
+++ b/indra/llcommon/lldependencies.h
@@ -8,21 +8,21 @@
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -30,6 +30,8 @@
#if ! defined(LL_LLDEPENDENCIES_H)
#define LL_LLDEPENDENCIES_H
+#include "linden_common.h"
+#include "llexception.h"
#include <string>
#include <vector>
#include <set>
@@ -40,7 +42,6 @@
#include <boost/range/iterator_range.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
-#include "llexception.h"
/*****************************************************************************
* Utilities
@@ -177,7 +178,7 @@ struct LLDependenciesEmpty
* values such as NULL or 0 rather than having to write
* LLDependenciesEmpty().
*/
- LLDependenciesEmpty(void*) {}
+ LLDependenciesEmpty(void*) {}
};
/**
@@ -209,7 +210,7 @@ class LLDependencies: public LLDependenciesBase
before(before_)
{}
NODE node;
- dep_set after, before;
+ dep_set after, before;
};
typedef std::map<KEY, DepNode> DepNodeMap;
typedef typename DepNodeMap::value_type DepNodeMapEntry;
@@ -239,7 +240,7 @@ public:
* NODE& reference.
*
* @note
- * Actual dependency analysis is deferred to the sort() method, so
+ * Actual dependency analysis is deferred to the sort() method, so
* you can add an arbitrary number of nodes without incurring analysis
* overhead for each. The flip side of this is that add()ing nodes that
* define a cycle leaves this object in a state in which sort() will
@@ -598,7 +599,7 @@ public:
return sorted_range(begin, end);
}
- using LLDependenciesBase::describe; // unhide virtual std::string describe(bool full=true) const;
+ using LLDependenciesBase::describe; // unhide virtual std::string describe(bool full=true) const;
/// Override base-class describe() with actual implementation
virtual std::ostream& describe(std::ostream& out, bool full=true) const
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index e4843a88eb..b6285db073 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* @file llerror.cpp
* @date December 2006
* @brief error message system
@@ -6,21 +6,21 @@
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -65,91 +65,91 @@
namespace {
#if LL_WINDOWS
- void debugger_print(const std::string& s)
- {
- // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
- // which works just fine under the windows debugger, but can cause users who
- // have enabled SEHOP exception chain validation to crash due to interactions
- // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
- //
- if (IsDebuggerPresent())
- {
- // Need UTF16 for Unicode OutputDebugString
- //
- if (s.size())
- {
- OutputDebugString(utf8str_to_utf16str(s).c_str());
- OutputDebugString(TEXT("\n"));
- }
- }
- }
+ void debugger_print(const std::string& s)
+ {
+ // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
+ // which works just fine under the windows debugger, but can cause users who
+ // have enabled SEHOP exception chain validation to crash due to interactions
+ // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
+ //
+ if (IsDebuggerPresent())
+ {
+ // Need UTF16 for Unicode OutputDebugString
+ //
+ if (s.size())
+ {
+ OutputDebugString(utf8str_to_utf16str(s).c_str());
+ OutputDebugString(TEXT("\n"));
+ }
+ }
+ }
#else
- class RecordToSyslog : public LLError::Recorder
- {
- public:
- RecordToSyslog(const std::string& identity)
- : mIdentity(identity)
- {
- openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0);
- // we need to set the string from a local copy of the string
- // since apparanetly openlog expects the const char* to remain
- // valid even after it returns (presumably until closelog)
- }
-
- ~RecordToSyslog()
- {
- closelog();
- }
+ class RecordToSyslog : public LLError::Recorder
+ {
+ public:
+ RecordToSyslog(const std::string& identity)
+ : mIdentity(identity)
+ {
+ openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0);
+ // we need to set the string from a local copy of the string
+ // since apparanetly openlog expects the const char* to remain
+ // valid even after it returns (presumably until closelog)
+ }
+
+ ~RecordToSyslog()
+ {
+ closelog();
+ }
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x01;
}
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- int syslogPriority = LOG_CRIT;
- switch (level) {
- case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break;
- case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break;
- case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break;
- case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break;
- default: syslogPriority = LOG_CRIT;
- }
-
- syslog(syslogPriority, "%s", message.c_str());
- }
- private:
- std::string mIdentity;
- };
+ int syslogPriority = LOG_CRIT;
+ switch (level) {
+ case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break;
+ case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break;
+ case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break;
+ case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break;
+ default: syslogPriority = LOG_CRIT;
+ }
+
+ syslog(syslogPriority, "%s", message.c_str());
+ }
+ private:
+ std::string mIdentity;
+ };
#endif
- class RecordToFile : public LLError::Recorder
- {
- public:
- RecordToFile(const std::string& filename):
- mName(filename)
- {
- mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
- if (!mFile)
- {
- LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
- }
- else
- {
- if (!LLError::getAlwaysFlush())
- {
- mFile.sync_with_stdio(false);
- }
- }
- }
-
- ~RecordToFile()
- {
- mFile.close();
- }
+ class RecordToFile : public LLError::Recorder
+ {
+ public:
+ RecordToFile(const std::string& filename):
+ mName(filename)
+ {
+ mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
+ if (!mFile)
+ {
+ LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
+ }
+ else
+ {
+ if (!LLError::getAlwaysFlush())
+ {
+ mFile.sync_with_stdio(false);
+ }
+ }
+ }
+
+ ~RecordToFile()
+ {
+ mFile.close();
+ }
virtual bool enabled() override
{
@@ -159,7 +159,7 @@ namespace {
return LLError::getEnabledLogTypesMask() & 0x02;
#endif
}
-
+
bool okay() const { return mFile.good(); }
std::string getFilename() const { return mName; }
@@ -178,25 +178,25 @@ namespace {
}
}
- private:
- const std::string mName;
- llofstream mFile;
- };
-
-
- class RecordToStderr : public LLError::Recorder
- {
- public:
- RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
- {
+ private:
+ const std::string mName;
+ llofstream mFile;
+ };
+
+
+ class RecordToStderr : public LLError::Recorder
+ {
+ public:
+ RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
+ {
this->showMultiline(true);
- }
-
+ }
+
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x04;
}
-
+
LL_FORCE_INLINE std::string createBoldANSI()
{
std::string ansi_code;
@@ -231,12 +231,12 @@ namespace {
return ansi_code;
}
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
// The default colors for error, warn and debug are now a bit more pastel
- // and easier to read on the default (black) terminal background but you
+ // and easier to read on the default (black) terminal background but you
// now have the option to set the color of each via an environment variables:
// LL_ANSI_ERROR_COLOR_CODE (default is red)
// LL_ANSI_WARN_COLOR_CODE (default is blue)
@@ -256,75 +256,75 @@ namespace {
static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue
static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta
- if (mUseANSI)
- {
+ if (mUseANSI)
+ {
writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
(level == LLError::LEVEL_WARN) ? s_ansi_warn :
s_ansi_debug, message);
- }
+ }
else
{
LL_PROFILE_ZONE_NAMED("fprintf");
fprintf(stderr, "%s\n", message.c_str());
}
- }
-
- private:
- bool mUseANSI;
+ }
+
+ private:
+ bool mUseANSI;
LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
- {
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
static std::string s_ansi_bold = createBoldANSI(); // bold text
static std::string s_ansi_reset = createResetANSI(); // reset
- // ANSI color code escape sequence, message, and reset in one fprintf call
+ // ANSI color code escape sequence, message, and reset in one fprintf call
// Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
- fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
- }
-
- static bool checkANSI(void)
- {
- // Check whether it's okay to use ANSI; if stderr is
- // a tty then we assume yes. Can be turned off with
- // the LL_NO_ANSI_COLOR env var.
- return (0 != isatty(2)) &&
- (NULL == getenv("LL_NO_ANSI_COLOR"));
- }
- };
-
- class RecordToFixedBuffer : public LLError::Recorder
- {
- public:
- RecordToFixedBuffer(LLLineBuffer* buffer)
+ fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
+ }
+
+ static bool checkANSI(void)
+ {
+ // Check whether it's okay to use ANSI; if stderr is
+ // a tty then we assume yes. Can be turned off with
+ // the LL_NO_ANSI_COLOR env var.
+ return (0 != isatty(2)) &&
+ (NULL == getenv("LL_NO_ANSI_COLOR"));
+ }
+ };
+
+ class RecordToFixedBuffer : public LLError::Recorder
+ {
+ public:
+ RecordToFixedBuffer(LLLineBuffer* buffer)
: mBuffer(buffer)
{
this->showMultiline(true);
this->showTags(false);
this->showLocation(false);
}
-
+
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x08;
}
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- mBuffer->addLine(message);
- }
-
- private:
- LLLineBuffer* mBuffer;
- };
+ mBuffer->addLine(message);
+ }
+
+ private:
+ LLLineBuffer* mBuffer;
+ };
#if LL_WINDOWS
- class RecordToWinDebug: public LLError::Recorder
- {
- public:
- RecordToWinDebug()
- {
+ class RecordToWinDebug: public LLError::Recorder
+ {
+ public:
+ RecordToWinDebug()
+ {
this->showMultiline(true);
this->showTags(false);
this->showLocation(false);
@@ -334,154 +334,154 @@ namespace {
{
return LLError::getEnabledLogTypesMask() & 0x10;
}
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- debugger_print(message);
- }
- };
+ debugger_print(message);
+ }
+ };
#endif
}
namespace
{
- std::string className(const std::type_info& type)
- {
- return LLError::Log::demangle(type.name());
- }
+ std::string className(const std::type_info& type)
+ {
+ return LLError::Log::demangle(type.name());
+ }
} // anonymous
namespace LLError
{
- std::string Log::demangle(const char* mangled)
- {
+ std::string Log::demangle(const char* mangled)
+ {
#ifdef __GNUC__
- // GCC: type_info::name() returns a mangled class name,st demangle
- // passing nullptr, 0 forces allocation of a unique buffer we can free
- // fixing MAINT-8724 on OSX 10.14
- int status = -1;
- char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
- std::string result(name ? name : mangled);
- free(name);
- return result;
+ // GCC: type_info::name() returns a mangled class name,st demangle
+ // passing nullptr, 0 forces allocation of a unique buffer we can free
+ // fixing MAINT-8724 on OSX 10.14
+ int status = -1;
+ char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
+ std::string result(name ? name : mangled);
+ free(name);
+ return result;
#elif LL_WINDOWS
- // Visual Studio: type_info::name() includes the text "class " at the start
- std::string name = mangled;
- for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
- {
- if (0 == name.compare(0, prefix.length(), prefix))
- {
- return name.substr(prefix.length());
- }
- }
- // huh, that's odd, we should see one or the other prefix -- but don't
- // try to log unless logging is already initialized
- // in Python, " or ".join(vector) -- but in C++, a PITB
- LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
- << name << "'" << LL_ENDL;
- return name;
+ // Visual Studio: type_info::name() includes the text "class " at the start
+ std::string name = mangled;
+ for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
+ {
+ if (0 == name.compare(0, prefix.length(), prefix))
+ {
+ return name.substr(prefix.length());
+ }
+ }
+ // huh, that's odd, we should see one or the other prefix -- but don't
+ // try to log unless logging is already initialized
+ // in Python, " or ".join(vector) -- but in C++, a PITB
+ LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
+ << name << "'" << LL_ENDL;
+ return name;
#else // neither GCC nor Visual Studio
- return mangled;
+ return mangled;
#endif
- }
+ }
} // LLError
namespace
{
- std::string functionName(const std::string& preprocessor_name)
- {
+ std::string functionName(const std::string& preprocessor_name)
+ {
#if LL_WINDOWS
- // DevStudio: the __FUNCTION__ macro string includes
- // the type and/or namespace prefixes
+ // DevStudio: the __FUNCTION__ macro string includes
+ // the type and/or namespace prefixes
- std::string::size_type p = preprocessor_name.rfind(':');
- if (p == std::string::npos)
- {
- return preprocessor_name;
- }
- return preprocessor_name.substr(p + 1);
+ std::string::size_type p = preprocessor_name.rfind(':');
+ if (p == std::string::npos)
+ {
+ return preprocessor_name;
+ }
+ return preprocessor_name.substr(p + 1);
#else
- return preprocessor_name;
+ return preprocessor_name;
#endif
- }
-
-
- class LogControlFile : public LLLiveFile
- {
- LOG_CLASS(LogControlFile);
-
- public:
- static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir);
-
- virtual bool loadFile();
-
- private:
- LogControlFile(const std::string &filename)
- : LLLiveFile(filename)
- { }
- };
-
- LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir)
- {
+ }
+
+
+ class LogControlFile : public LLLiveFile
+ {
+ LOG_CLASS(LogControlFile);
+
+ public:
+ static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir);
+
+ virtual bool loadFile();
+
+ private:
+ LogControlFile(const std::string &filename)
+ : LLLiveFile(filename)
+ { }
+ };
+
+ LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir)
+ {
// NB: We have no abstraction in llcommon for the "proper"
// delimiter but it turns out that "/" works on all three platforms
-
- std::string file = user_dir + "/logcontrol-dev.xml";
-
- llstat stat_info;
- if (LLFile::stat(file, &stat_info)) {
- // NB: stat returns non-zero if it can't read the file, for example
- // if it doesn't exist. LLFile has no better abstraction for
- // testing for file existence.
-
- file = app_dir + "/logcontrol.xml";
- }
- return * new LogControlFile(file);
- // NB: This instance is never freed
- }
-
- bool LogControlFile::loadFile()
- {
- LLSD configuration;
-
- {
- llifstream file(filename().c_str());
- if (!file.is_open())
- {
- LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL;
- return false;
- }
-
- if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE)
- {
- LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL;
- return false;
- }
-
- if (! configuration || !configuration.isMap())
- {
- LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
- " content; not changing configuration"
- << LL_ENDL;
- return false;
- }
- }
-
- LLError::configure(configuration);
- LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL;
- return true;
- }
-
-
- typedef std::map<std::string, LLError::ELevel> LevelMap;
- typedef std::vector<LLError::RecorderPtr> Recorders;
- typedef std::vector<LLError::CallSite*> CallSiteVector;
+
+ std::string file = user_dir + "/logcontrol-dev.xml";
+
+ llstat stat_info;
+ if (LLFile::stat(file, &stat_info)) {
+ // NB: stat returns non-zero if it can't read the file, for example
+ // if it doesn't exist. LLFile has no better abstraction for
+ // testing for file existence.
+
+ file = app_dir + "/logcontrol.xml";
+ }
+ return * new LogControlFile(file);
+ // NB: This instance is never freed
+ }
+
+ bool LogControlFile::loadFile()
+ {
+ LLSD configuration;
+
+ {
+ llifstream file(filename().c_str());
+ if (!file.is_open())
+ {
+ LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL;
+ return false;
+ }
+
+ if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE)
+ {
+ LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL;
+ return false;
+ }
+
+ if (! configuration || !configuration.isMap())
+ {
+ LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
+ " content; not changing configuration"
+ << LL_ENDL;
+ return false;
+ }
+ }
+
+ LLError::configure(configuration);
+ LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL;
+ return true;
+ }
+
+
+ typedef std::map<std::string, LLError::ELevel> LevelMap;
+ typedef std::vector<LLError::RecorderPtr> Recorders;
+ typedef std::vector<LLError::CallSite*> CallSiteVector;
class SettingsConfig : public LLRefCount
{
@@ -492,9 +492,9 @@ namespace
LLError::ELevel mDefaultLevel;
- bool mLogAlwaysFlush;
+ bool mLogAlwaysFlush;
- U32 mEnabledLogTypesMask;
+ U32 mEnabledLogTypesMask;
LevelMap mFunctionLevelMap;
LevelMap mClassLevelMap;
@@ -539,34 +539,34 @@ namespace
mRecorders.clear();
}
- class Globals
- {
+ class Globals
+ {
public:
static Globals* getInstance();
protected:
- Globals();
- public:
- std::string mFatalMessage;
+ Globals();
+ public:
+ std::string mFatalMessage;
- void addCallSite(LLError::CallSite&);
- void invalidateCallSites();
+ void addCallSite(LLError::CallSite&);
+ void invalidateCallSites();
SettingsConfigPtr getSettingsConfig();
void resetSettingsConfig();
LLError::SettingsStoragePtr saveAndResetSettingsConfig();
void restore(LLError::SettingsStoragePtr pSettingsStorage);
- private:
- CallSiteVector callSites;
+ private:
+ CallSiteVector callSites;
SettingsConfigPtr mSettingsConfig;
- };
+ };
- Globals::Globals()
- :
- callSites(),
+ Globals::Globals()
+ :
+ callSites(),
mSettingsConfig(new SettingsConfig())
- {
- }
+ {
+ }
Globals* Globals::getInstance()
@@ -579,20 +579,20 @@ namespace
return &inst;
}
- void Globals::addCallSite(LLError::CallSite& site)
- {
- callSites.push_back(&site);
- }
-
- void Globals::invalidateCallSites()
- {
- for (LLError::CallSite* site : callSites)
- {
+ void Globals::addCallSite(LLError::CallSite& site)
+ {
+ callSites.push_back(&site);
+ }
+
+ void Globals::invalidateCallSites()
+ {
+ for (LLError::CallSite* site : callSites)
+ {
site->invalidate();
- }
-
- callSites.clear();
- }
+ }
+
+ callSites.clear();
+ }
SettingsConfigPtr Globals::getSettingsConfig()
{
@@ -622,77 +622,77 @@ namespace
namespace LLError
{
- CallSite::CallSite(ELevel level,
- const char* file,
- int line,
- const std::type_info& class_info,
- const char* function,
- bool printOnce,
- const char** tags,
- size_t tag_count)
- : mLevel(level),
- mFile(file),
- mLine(line),
- mClassInfo(class_info),
- mFunction(function),
- mCached(false),
- mShouldLog(false),
- mPrintOnce(printOnce),
- mTags(new const char* [tag_count]),
- mTagCount(tag_count)
- {
- switch (mLevel)
- {
+ CallSite::CallSite(ELevel level,
+ const char* file,
+ int line,
+ const std::type_info& class_info,
+ const char* function,
+ bool printOnce,
+ const char** tags,
+ size_t tag_count)
+ : mLevel(level),
+ mFile(file),
+ mLine(line),
+ mClassInfo(class_info),
+ mFunction(function),
+ mCached(false),
+ mShouldLog(false),
+ mPrintOnce(printOnce),
+ mTags(new const char* [tag_count]),
+ mTagCount(tag_count)
+ {
+ switch (mLevel)
+ {
case LEVEL_DEBUG: mLevelString = "DEBUG"; break;
case LEVEL_INFO: mLevelString = "INFO"; break;
case LEVEL_WARN: mLevelString = "WARNING"; break;
case LEVEL_ERROR: mLevelString = "ERROR"; break;
default: mLevelString = "XXX"; break;
- };
+ };
- mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine);
+ mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine);
#if LL_WINDOWS
- // DevStudio: __FUNCTION__ already includes the full class name
+ // DevStudio: __FUNCTION__ already includes the full class name
#else
#if LL_LINUX
- // gross, but typeid comparison seems to always fail here with gcc4.1
- if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name()))
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name()))
#else
- if (mClassInfo != typeid(NoClassInfo))
+ if (mClassInfo != typeid(NoClassInfo))
#endif // LL_LINUX
- {
- mFunctionString = className(mClassInfo) + "::";
- }
+ {
+ mFunctionString = className(mClassInfo) + "::";
+ }
#endif
- mFunctionString += std::string(mFunction);
+ mFunctionString += std::string(mFunction);
- for (int i = 0; i < tag_count; i++)
- {
+ for (int i = 0; i < tag_count; i++)
+ {
if (strchr(tags[i], ' '))
{
LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL;
}
- mTags[i] = tags[i];
- }
+ mTags[i] = tags[i];
+ }
mTagString.append("#");
// always construct a tag sequence; will be just a single # if no tag
- for (size_t i = 0; i < mTagCount; i++)
- {
- mTagString.append(mTags[i]);
+ for (size_t i = 0; i < mTagCount; i++)
+ {
+ mTagString.append(mTags[i]);
mTagString.append("#");
- }
- }
-
- CallSite::~CallSite()
- {
- delete []mTags;
- }
-
- void CallSite::invalidate()
- {
- mCached = false;
- }
+ }
+ }
+
+ CallSite::~CallSite()
+ {
+ delete []mTags;
+ }
+
+ void CallSite::invalidate()
+ {
+ mCached = false;
+ }
}
namespace
@@ -729,202 +729,202 @@ namespace
#endif
}
- bool stderrLogWantsTime()
- {
+ bool stderrLogWantsTime()
+ {
#if LL_WINDOWS
- return false;
+ return false;
#else
- return true;
+ return true;
#endif
- }
-
-
- void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
- {
- Globals::getInstance()->resetSettingsConfig();
-
- LLError::setDefaultLevel(LLError::LEVEL_INFO);
- LLError::setAlwaysFlush(true);
- LLError::setEnabledLogTypesMask(0xFFFFFFFF);
- LLError::setTimeFunction(LLError::utcTime);
-
- // log_to_stderr is only false in the unit and integration tests to keep builds quieter
- if (log_to_stderr && shouldLogToStderr())
- {
- LLError::logToStderr();
- }
+ }
+
+
+ void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
+ {
+ Globals::getInstance()->resetSettingsConfig();
+
+ LLError::setDefaultLevel(LLError::LEVEL_INFO);
+ LLError::setAlwaysFlush(true);
+ LLError::setEnabledLogTypesMask(0xFFFFFFFF);
+ LLError::setTimeFunction(LLError::utcTime);
+
+ // log_to_stderr is only false in the unit and integration tests to keep builds quieter
+ if (log_to_stderr && shouldLogToStderr())
+ {
+ LLError::logToStderr();
+ }
#if LL_WINDOWS
- LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
- LLError::addRecorder(recordToWinDebug);
+ LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
+ LLError::addRecorder(recordToWinDebug);
#endif
- LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir);
-
- // NOTE: We want to explicitly load the file before we add it to the event timer
- // that checks for changes to the file. Else, we're not actually loading the file yet,
- // and most of the initialization happens without any attention being paid to the
- // log control file. Not to mention that when it finally gets checked later,
- // all log statements that have been evaluated already become dirty and need to be
- // evaluated for printing again. So, make sure to call checkAndReload()
- // before addToEventTimer().
- e.checkAndReload();
- e.addToEventTimer();
- }
+ LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir);
+
+ // NOTE: We want to explicitly load the file before we add it to the event timer
+ // that checks for changes to the file. Else, we're not actually loading the file yet,
+ // and most of the initialization happens without any attention being paid to the
+ // log control file. Not to mention that when it finally gets checked later,
+ // all log statements that have been evaluated already become dirty and need to be
+ // evaluated for printing again. So, make sure to call checkAndReload()
+ // before addToEventTimer().
+ e.checkAndReload();
+ e.addToEventTimer();
+ }
}
namespace LLError
{
- void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr)
- {
- commonInit(user_dir, app_dir, log_to_stderr);
- }
-
- void setFatalFunction(const FatalFunction& f)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mCrashFunction = f;
- }
-
- FatalFunction getFatalFunction()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mCrashFunction;
- }
-
- std::string getFatalMessage()
- {
- return Globals::getInstance()->mFatalMessage;
- }
-
- void setTimeFunction(TimeFunction f)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mTimeFunction = f;
- }
-
- void setDefaultLevel(ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mDefaultLevel = level;
- }
-
- ELevel getDefaultLevel()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mDefaultLevel;
- }
-
- void setAlwaysFlush(bool flush)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mLogAlwaysFlush = flush;
- }
-
- bool getAlwaysFlush()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mLogAlwaysFlush;
- }
-
- void setEnabledLogTypesMask(U32 mask)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mEnabledLogTypesMask = mask;
- }
-
- U32 getEnabledLogTypesMask()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mEnabledLogTypesMask;
- }
-
- void setFunctionLevel(const std::string& function_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mFunctionLevelMap[function_name] = level;
- }
-
- void setClassLevel(const std::string& class_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mClassLevelMap[class_name] = level;
- }
-
- void setFileLevel(const std::string& file_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mFileLevelMap[file_name] = level;
- }
-
- void setTagLevel(const std::string& tag_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mTagLevelMap[tag_name] = level;
- }
-
- LLError::ELevel decodeLevel(std::string name)
- {
- static LevelMap level_names;
- if (level_names.empty())
- {
- level_names["ALL"] = LLError::LEVEL_ALL;
- level_names["DEBUG"] = LLError::LEVEL_DEBUG;
- level_names["INFO"] = LLError::LEVEL_INFO;
- level_names["WARN"] = LLError::LEVEL_WARN;
- level_names["ERROR"] = LLError::LEVEL_ERROR;
- level_names["NONE"] = LLError::LEVEL_NONE;
- }
-
- std::transform(name.begin(), name.end(), name.begin(), toupper);
-
- LevelMap::const_iterator i = level_names.find(name);
- if (i == level_names.end())
- {
- LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL;
- return LLError::LEVEL_INFO;
- }
-
- return i->second;
- }
+ void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr)
+ {
+ commonInit(user_dir, app_dir, log_to_stderr);
+ }
+
+ void setFatalFunction(const FatalFunction& f)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mCrashFunction = f;
+ }
+
+ FatalFunction getFatalFunction()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mCrashFunction;
+ }
+
+ std::string getFatalMessage()
+ {
+ return Globals::getInstance()->mFatalMessage;
+ }
+
+ void setTimeFunction(TimeFunction f)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mTimeFunction = f;
+ }
+
+ void setDefaultLevel(ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mDefaultLevel = level;
+ }
+
+ ELevel getDefaultLevel()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mDefaultLevel;
+ }
+
+ void setAlwaysFlush(bool flush)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mLogAlwaysFlush = flush;
+ }
+
+ bool getAlwaysFlush()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mLogAlwaysFlush;
+ }
+
+ void setEnabledLogTypesMask(U32 mask)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mEnabledLogTypesMask = mask;
+ }
+
+ U32 getEnabledLogTypesMask()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mEnabledLogTypesMask;
+ }
+
+ void setFunctionLevel(const std::string& function_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mFunctionLevelMap[function_name] = level;
+ }
+
+ void setClassLevel(const std::string& class_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mClassLevelMap[class_name] = level;
+ }
+
+ void setFileLevel(const std::string& file_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mFileLevelMap[file_name] = level;
+ }
+
+ void setTagLevel(const std::string& tag_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mTagLevelMap[tag_name] = level;
+ }
+
+ LLError::ELevel decodeLevel(std::string name)
+ {
+ static LevelMap level_names;
+ if (level_names.empty())
+ {
+ level_names["ALL"] = LLError::LEVEL_ALL;
+ level_names["DEBUG"] = LLError::LEVEL_DEBUG;
+ level_names["INFO"] = LLError::LEVEL_INFO;
+ level_names["WARN"] = LLError::LEVEL_WARN;
+ level_names["ERROR"] = LLError::LEVEL_ERROR;
+ level_names["NONE"] = LLError::LEVEL_NONE;
+ }
+
+ std::transform(name.begin(), name.end(), name.begin(), toupper);
+
+ LevelMap::const_iterator i = level_names.find(name);
+ if (i == level_names.end())
+ {
+ LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL;
+ return LLError::LEVEL_INFO;
+ }
+
+ return i->second;
+ }
}
namespace {
- void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
- {
- LLSD::array_const_iterator i, end;
- for (i = list.beginArray(), end = list.endArray(); i != end; ++i)
- {
- map[*i] = level;
- }
- }
+ void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
+ {
+ LLSD::array_const_iterator i, end;
+ for (i = list.beginArray(), end = list.endArray(); i != end; ++i)
+ {
+ map[*i] = level;
+ }
+ }
}
namespace LLError
{
- void configure(const LLSD& config)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- s->mFunctionLevelMap.clear();
- s->mClassLevelMap.clear();
- s->mFileLevelMap.clear();
- s->mTagLevelMap.clear();
- s->mUniqueLogMessages.clear();
-
- setDefaultLevel(decodeLevel(config["default-level"]));
+ void configure(const LLSD& config)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ s->mFunctionLevelMap.clear();
+ s->mClassLevelMap.clear();
+ s->mFileLevelMap.clear();
+ s->mTagLevelMap.clear();
+ s->mUniqueLogMessages.clear();
+
+ setDefaultLevel(decodeLevel(config["default-level"]));
if (config.has("log-always-flush"))
{
setAlwaysFlush(config["log-always-flush"]);
@@ -933,7 +933,7 @@ namespace LLError
{
setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger());
}
-
+
if (config.has("settings") && config["settings"].isArray())
{
LLSD sets = config["settings"];
@@ -952,66 +952,66 @@ namespace LLError
}
}
}
- }
+ }
}
namespace LLError
{
- Recorder::Recorder()
- : mWantsTime(true)
+ Recorder::Recorder()
+ : mWantsTime(true)
, mWantsTags(true)
, mWantsLevel(true)
, mWantsLocation(true)
, mWantsFunctionName(true)
, mWantsMultiline(false)
- {
- }
-
- Recorder::~Recorder()
- {
- }
-
- bool Recorder::wantsTime()
- {
- return mWantsTime;
- }
-
- // virtual
- bool Recorder::wantsTags()
- {
- return mWantsTags;
- }
-
- // virtual
- bool Recorder::wantsLevel()
- {
- return mWantsLevel;
- }
-
- // virtual
- bool Recorder::wantsLocation()
- {
- return mWantsLocation;
- }
-
- // virtual
- bool Recorder::wantsFunctionName()
- {
- return mWantsFunctionName;
- }
-
- // virtual
- bool Recorder::wantsMultiline()
- {
- return mWantsMultiline;
- }
+ {
+ }
+
+ Recorder::~Recorder()
+ {
+ }
+
+ bool Recorder::wantsTime()
+ {
+ return mWantsTime;
+ }
+
+ // virtual
+ bool Recorder::wantsTags()
+ {
+ return mWantsTags;
+ }
+
+ // virtual
+ bool Recorder::wantsLevel()
+ {
+ return mWantsLevel;
+ }
+
+ // virtual
+ bool Recorder::wantsLocation()
+ {
+ return mWantsLocation;
+ }
+
+ // virtual
+ bool Recorder::wantsFunctionName()
+ {
+ return mWantsFunctionName;
+ }
+
+ // virtual
+ bool Recorder::wantsMultiline()
+ {
+ return mWantsMultiline;
+ }
void Recorder::showTime(bool show)
{
mWantsTime = show;
}
-
+
void Recorder::showTags(bool show)
{
mWantsTags = show;
@@ -1037,28 +1037,28 @@ namespace LLError
mWantsMultiline = show;
}
- void addRecorder(RecorderPtr recorder)
- {
- if (!recorder)
- {
- return;
- }
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- s->mRecorders.push_back(recorder);
- }
-
- void removeRecorder(RecorderPtr recorder)
- {
- if (!recorder)
- {
- return;
- }
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
- s->mRecorders.end());
- }
+ void addRecorder(RecorderPtr recorder)
+ {
+ if (!recorder)
+ {
+ return;
+ }
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ s->mRecorders.push_back(recorder);
+ }
+
+ void removeRecorder(RecorderPtr recorder)
+ {
+ if (!recorder)
+ {
+ return;
+ }
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
+ s->mRecorders.end());
+ }
// Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
// a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
@@ -1127,26 +1127,26 @@ namespace LLError
namespace LLError
{
- void logToFile(const std::string& file_name)
- {
- // remove any previous Recorder filling this role
- removeRecorder<RecordToFile>();
-
- if (!file_name.empty())
- {
- std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
- if (recordToFile->okay())
- {
- addRecorder(recordToFile);
- }
- }
- }
-
- std::string logFileName()
- {
- auto found = findRecorder<RecordToFile>();
- return found? found->getFilename() : std::string();
- }
+ void logToFile(const std::string& file_name)
+ {
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFile>();
+
+ if (!file_name.empty())
+ {
+ std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
+ if (recordToFile->okay())
+ {
+ addRecorder(recordToFile);
+ }
+ }
+ }
+
+ std::string logFileName()
+ {
+ auto found = findRecorder<RecordToFile>();
+ return found? found->getFilename() : std::string();
+ }
void logToStderr()
{
@@ -1157,17 +1157,17 @@ namespace LLError
}
}
- void logToFixedBuffer(LLLineBuffer* fixedBuffer)
- {
- // remove any previous Recorder filling this role
- removeRecorder<RecordToFixedBuffer>();
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
+ {
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFixedBuffer>();
- if (fixedBuffer)
- {
- RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
- addRecorder(recordToFixedBuffer);
- }
- }
+ if (fixedBuffer)
+ {
+ RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
+ addRecorder(recordToFixedBuffer);
+ }
+ }
}
namespace
@@ -1213,40 +1213,40 @@ namespace
return out.str();
}
- void writeToRecorders(const LLError::CallSite& site, const std::string& message)
- {
+ void writeToRecorders(const LLError::CallSite& site, const std::string& message)
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLError::ELevel level = site.mLevel;
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLError::ELevel level = site.mLevel;
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
std::string escaped_message;
LLMutexLock lock(&s->mRecorderMutex);
- for (LLError::RecorderPtr& r : s->mRecorders)
- {
+ for (LLError::RecorderPtr& r : s->mRecorders)
+ {
if (!r->enabled())
{
continue;
}
+
+ std::ostringstream message_stream;
- std::ostringstream message_stream;
-
- if (r->wantsTime() && s->mTimeFunction != NULL)
- {
- message_stream << s->mTimeFunction();
- }
+ if (r->wantsTime() && s->mTimeFunction != NULL)
+ {
+ message_stream << s->mTimeFunction();
+ }
message_stream << " ";
-
- if (r->wantsLevel())
+
+ if (r->wantsLevel())
{
- message_stream << site.mLevelString;
+ message_stream << site.mLevelString;
}
message_stream << " ";
-
- if (r->wantsTags())
- {
- message_stream << site.mTagString;
- }
+
+ if (r->wantsTags())
+ {
+ message_stream << site.mTagString;
+ }
message_stream << " ";
if (r->wantsLocation() || level == LLError::LEVEL_ERROR)
@@ -1255,10 +1255,10 @@ namespace
}
message_stream << " ";
- if (r->wantsFunctionName())
- {
- message_stream << site.mFunctionString;
- }
+ if (r->wantsFunctionName())
+ {
+ message_stream << site.mFunctionString;
+ }
message_stream << " : ";
if (r->wantsMultiline())
@@ -1274,250 +1274,251 @@ namespace
message_stream << escaped_message;
}
- r->recordMessage(level, message_stream.str());
- }
- }
+ r->recordMessage(level, message_stream.str());
+ }
+ }
}
namespace {
- // We need a couple different mutexes, but we want to use the same mechanism
- // for both. Make getMutex() a template function with different instances
- // for different MutexDiscriminator values.
- enum MutexDiscriminator
- {
- LOG_MUTEX,
- STACKS_MUTEX
- };
- // Some logging calls happen very early in processing -- so early that our
- // module-static variables aren't yet initialized. getMutex() wraps a
- // function-static LLMutex so that early calls can still have a valid
- // LLMutex instance.
- template <MutexDiscriminator MTX>
- LLMutex* getMutex()
- {
- // guaranteed to be initialized the first time control reaches here
- static LLMutex sMutex;
- return &sMutex;
- }
-
- bool checkLevelMap(const LevelMap& map, const std::string& key,
- LLError::ELevel& level)
- {
- bool stop_checking;
- LevelMap::const_iterator i = map.find(key);
- if (i == map.end())
- {
- return stop_checking = false;
- }
-
- level = i->second;
- return stop_checking = true;
- }
-
- bool checkLevelMap( const LevelMap& map,
- const char *const * keys,
- size_t count,
- LLError::ELevel& level)
- {
- bool found_level = false;
-
- LLError::ELevel tag_level = LLError::LEVEL_NONE;
-
- for (size_t i = 0; i < count; i++)
- {
- LevelMap::const_iterator it = map.find(keys[i]);
- if (it != map.end())
- {
- found_level = true;
- tag_level = llmin(tag_level, it->second);
- }
- }
-
- if (found_level)
- {
- level = tag_level;
- }
- return found_level;
- }
+ // We need a couple different mutexes, but we want to use the same mechanism
+ // for both. Make getMutex() a template function with different instances
+ // for different MutexDiscriminator values.
+ enum MutexDiscriminator
+ {
+ LOG_MUTEX,
+ STACKS_MUTEX
+ };
+ // Some logging calls happen very early in processing -- so early that our
+ // module-static variables aren't yet initialized. getMutex() wraps a
+ // function-static LLMutex so that early calls can still have a valid
+ // LLMutex instance.
+ template <MutexDiscriminator MTX>
+ LLMutex* getMutex()
+ {
+ // guaranteed to be initialized the first time control reaches here
+ static LLMutex sMutex;
+ return &sMutex;
+ }
+
+ bool checkLevelMap(const LevelMap& map, const std::string& key,
+ LLError::ELevel& level)
+ {
+ bool stop_checking;
+ LevelMap::const_iterator i = map.find(key);
+ if (i == map.end())
+ {
+ return stop_checking = false;
+ }
+
+ level = i->second;
+ return stop_checking = true;
+ }
+
+ bool checkLevelMap( const LevelMap& map,
+ const char *const * keys,
+ size_t count,
+ LLError::ELevel& level)
+ {
+ bool found_level = false;
+
+ LLError::ELevel tag_level = LLError::LEVEL_NONE;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ LevelMap::const_iterator it = map.find(keys[i]);
+ if (it != map.end())
+ {
+ found_level = true;
+ tag_level = llmin(tag_level, it->second);
+ }
+ }
+
+ if (found_level)
+ {
+ level = tag_level;
+ }
+ return found_level;
+ }
}
namespace LLError
{
- bool Log::shouldLog(CallSite& site)
- {
+ bool Log::shouldLog(CallSite& site)
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
- if (!lock.isLocked())
- {
- return false;
- }
-
- Globals *g = Globals::getInstance();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- s->mShouldLogCallCounter++;
-
- const std::string& class_name = className(site.mClassInfo);
- std::string function_name = functionName(site.mFunction);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return false;
+ }
+
+ Globals *g = Globals::getInstance();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ s->mShouldLogCallCounter++;
+
+ const std::string& class_name = className(site.mClassInfo);
+ std::string function_name = functionName(site.mFunction);
#if LL_LINUX
- // gross, but typeid comparison seems to always fail here with gcc4.1
- if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
#else
- if (site.mClassInfo != typeid(NoClassInfo))
+ if (site.mClassInfo != typeid(NoClassInfo))
#endif // LL_LINUX
- {
- function_name = class_name + "::" + function_name;
- }
-
- ELevel compareLevel = s->mDefaultLevel;
-
- // The most specific match found will be used as the log level,
- // since the computation short circuits.
- // So, in increasing order of importance:
- // Default < Tags < File < Class < Function
- checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel)
- || checkLevelMap(s->mClassLevelMap, class_name, compareLevel)
- || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel)
- || (site.mTagCount > 0
- ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel)
- : false);
-
- site.mCached = true;
- g->addCallSite(site);
- return site.mShouldLog = site.mLevel >= compareLevel;
- }
-
-
- void Log::flush(const std::ostringstream& out, const CallSite& site)
- {
+ {
+ function_name = class_name + "::" + function_name;
+ }
+
+ ELevel compareLevel = s->mDefaultLevel;
+
+ // The most specific match found will be used as the log level,
+ // since the computation short circuits.
+ // So, in increasing order of importance:
+ // Default < Tags < File < Class < Function
+ checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel)
+ || checkLevelMap(s->mClassLevelMap, class_name, compareLevel)
+ || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel)
+ || (site.mTagCount > 0
+ ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel)
+ : false);
+
+ site.mCached = true;
+ g->addCallSite(site);
+ return site.mShouldLog = site.mLevel >= compareLevel;
+ }
+
+
+ void Log::flush(const std::ostringstream& out, const CallSite& site)
+ {
LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
- if (!lock.isLocked())
- {
- return;
- }
-
- Globals* g = Globals::getInstance();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- std::string message = out.str();
-
- if (site.mPrintOnce)
- {
- std::ostringstream message_stream;
-
- std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);
- if (messageIter != s->mUniqueLogMessages.end())
- {
- messageIter->second++;
- unsigned int num_messages = messageIter->second;
- if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
- {
- message_stream << "ONCE (" << num_messages << "th time seen): ";
- }
- else
- {
- return;
- }
- }
- else
- {
- message_stream << "ONCE: ";
- s->mUniqueLogMessages[message] = 1;
- }
- message_stream << message;
- message = message_stream.str();
- }
-
- writeToRecorders(site, message);
-
- if (site.mLevel == LEVEL_ERROR)
- {
- g->mFatalMessage = message;
- if (s->mCrashFunction)
- {
- s->mCrashFunction(message);
- }
- }
- }
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ Globals* g = Globals::getInstance();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ std::string message = out.str();
+
+ if (site.mPrintOnce)
+ {
+ std::ostringstream message_stream;
+
+ std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);
+ if (messageIter != s->mUniqueLogMessages.end())
+ {
+ messageIter->second++;
+ unsigned int num_messages = messageIter->second;
+ if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
+ {
+ message_stream << "ONCE (" << num_messages << "th time seen): ";
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ message_stream << "ONCE: ";
+ s->mUniqueLogMessages[message] = 1;
+ }
+ message_stream << message;
+ message = message_stream.str();
+ }
+
+ writeToRecorders(site, message);
+
+ if (site.mLevel == LEVEL_ERROR)
+ {
+ writeToRecorders(site, stringize(boost::stacktrace::stacktrace()));
+ g->mFatalMessage = message;
+ if (s->mCrashFunction)
+ {
+ s->mCrashFunction(message);
+ }
+ }
+ }
}
namespace LLError
{
- SettingsStoragePtr saveAndResetSettings()
- {
- return Globals::getInstance()->saveAndResetSettingsConfig();
- }
-
- void restoreSettings(SettingsStoragePtr pSettingsStorage)
- {
- return Globals::getInstance()->restore(pSettingsStorage);
- }
-
- std::string removePrefix(std::string& s, const std::string& p)
- {
- std::string::size_type where = s.find(p);
- if (where == std::string::npos)
- {
- return s;
- }
-
- return std::string(s, where + p.size());
- }
-
- void replaceChar(std::string& s, char old, char replacement)
- {
- std::string::size_type i = 0;
- std::string::size_type len = s.length();
- for ( ; i < len; i++ )
- {
- if (s[i] == old)
- {
- s[i] = replacement;
- }
- }
- }
-
- std::string abbreviateFile(const std::string& filePath)
- {
- std::string f = filePath;
+ SettingsStoragePtr saveAndResetSettings()
+ {
+ return Globals::getInstance()->saveAndResetSettingsConfig();
+ }
+
+ void restoreSettings(SettingsStoragePtr pSettingsStorage)
+ {
+ return Globals::getInstance()->restore(pSettingsStorage);
+ }
+
+ std::string removePrefix(std::string& s, const std::string& p)
+ {
+ std::string::size_type where = s.find(p);
+ if (where == std::string::npos)
+ {
+ return s;
+ }
+
+ return std::string(s, where + p.size());
+ }
+
+ void replaceChar(std::string& s, char old, char replacement)
+ {
+ std::string::size_type i = 0;
+ std::string::size_type len = s.length();
+ for ( ; i < len; i++ )
+ {
+ if (s[i] == old)
+ {
+ s[i] = replacement;
+ }
+ }
+ }
+
+ std::string abbreviateFile(const std::string& filePath)
+ {
+ std::string f = filePath;
#if LL_WINDOWS
- replaceChar(f, '\\', '/');
+ replaceChar(f, '\\', '/');
#endif
- static std::string indra_prefix = "indra/";
- f = removePrefix(f, indra_prefix);
+ static std::string indra_prefix = "indra/";
+ f = removePrefix(f, indra_prefix);
#if LL_DARWIN
- static std::string newview_prefix = "newview/../";
- f = removePrefix(f, newview_prefix);
+ static std::string newview_prefix = "newview/../";
+ f = removePrefix(f, newview_prefix);
#endif
- return f;
- }
-
- int shouldLogCallCount()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mShouldLogCallCounter;
- }
-
- std::string utcTime()
- {
- time_t now = time(NULL);
- const size_t BUF_SIZE = 64;
- char time_str[BUF_SIZE]; /* Flawfinder: ignore */
-
- auto chars = strftime(time_str, BUF_SIZE,
- "%Y-%m-%dT%H:%M:%SZ",
- gmtime(&now));
-
- return chars ? time_str : "time error";
- }
+ return f;
+ }
+
+ int shouldLogCallCount()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mShouldLogCallCounter;
+ }
+
+ std::string utcTime()
+ {
+ time_t now = time(NULL);
+ const size_t BUF_SIZE = 64;
+ char time_str[BUF_SIZE]; /* Flawfinder: ignore */
+
+ auto chars = strftime(time_str, BUF_SIZE,
+ "%Y-%m-%dT%H:%M:%SZ",
+ gmtime(&now));
+
+ return chars ? time_str : "time error";
+ }
}
namespace LLError
-{
+{
LLCallStacks::StringVector LLCallStacks::sBuffer ;
//static
@@ -1576,7 +1577,7 @@ namespace LLError
LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend());
ri != re; ++ri)
- {
+ {
LL_INFOS() << (*ri) << LL_ENDL;
}
LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
diff --git a/indra/llcommon/llerrorlegacy.h b/indra/llcommon/llerrorlegacy.h
deleted file mode 100644
index 693e1501d5..0000000000
--- a/indra/llcommon/llerrorlegacy.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @file llerrorlegacy.h
- * @date January 2007
- * @brief old things from the older error system
- *
- * $LicenseInfo:firstyear=2007&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#ifndef LL_LLERRORLEGACY_H
-#define LL_LLERRORLEGACY_H
-
-
-#endif // LL_LLERRORLEGACY_H
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index e1fc4764f6..d651aae39c 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-04-29
* @brief Implementation for lleventcoro.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -119,7 +119,7 @@ void llcoro::suspendUntilTimeout(float seconds)
// We used to call boost::this_fiber::sleep_for(). But some coroutines
// (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout()
// loop, in which case a sleep_for() call risks sleeping through shutdown.
- // So instead, listen for "LLApp" state-changing events -- which
+ // So instead, listen for LLApp state-changing events -- which
// fortunately is handled for us by suspendUntilEventOnWithTimeout().
// Wait for an event on a bogus LLEventPump on which nobody ever posts
// events. Don't make it static because that would force instantiation of
@@ -132,8 +132,8 @@ void llcoro::suspendUntilTimeout(float seconds)
// Timeout is the NORMAL case for this call!
static LLSD timedout;
// Deliver, but ignore, timedout when (as usual) we did not receive any
- // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will
- // itself throw Stopping when "LLApp" starts broadcasting shutdown events.
+ // LLApp event. The point is that suspendUntilEventOnWithTimeout() will
+ // itself throw Stopping when LLApp starts broadcasting shutdown events.
suspendUntilEventOnWithTimeout(bogus, seconds, timedout);
}
@@ -167,33 +167,26 @@ postAndSuspendSetup(const std::string& callerName,
// "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
// notice the pending LLApp status first.
LLBoundListener stopper(
- LLEventPumps::instance().obtain("LLApp").listen(
+ LLCoros::getStopListener(
listenerName,
+ LLCoros::instance().getName(),
[&promise, listenerName](const LLSD& status)
{
- // anything except "running" should wake up the waiting
- // coroutine
- auto& statsd = status["status"];
- if (statsd.asString() != "running")
+ LL_DEBUGS("lleventcoro") << listenerName
+ << " spotted status " << status
+ << ", throwing Stopping" << LL_ENDL;
+ try
+ {
+ promise.set_exception(
+ std::make_exception_ptr(
+ LLCoros::Stopping("status " + stringize(status))));
+ }
+ catch (const boost::fibers::promise_already_satisfied&)
{
- LL_DEBUGS("lleventcoro") << listenerName
- << " spotted status " << statsd
- << ", throwing Stopping" << LL_ENDL;
- try
- {
- promise.set_exception(
- std::make_exception_ptr(
- LLCoros::Stopping("status " + statsd.asString())));
- }
- catch (const boost::fibers::promise_already_satisfied&)
- {
- LL_WARNS("lleventcoro") << listenerName
- << " couldn't throw Stopping "
- "because promise already set" << LL_ENDL;
- }
+ LL_WARNS("lleventcoro") << listenerName
+ << " couldn't throw Stopping "
+ "because promise already set" << LL_ENDL;
}
- // do not consume -- every listener must see status
- return false;
}));
LLBoundListener connection(
replyPump.listen(
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 4c3c0f3414..5adaa3ebae 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -6,25 +6,25 @@
* useful when you have a single LLEventPump listener on which you can
* request different operations, vs. instantiating a different
* LLEventPump for each such operation.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -35,7 +35,6 @@
#include <boost/fiber/fss.hpp>
#include <boost/function_types/is_member_function_pointer.hpp>
#include <boost/function_types/is_nonmember_callable_builtin.hpp>
-#include <boost/hof/is_invocable.hpp> // until C++17, when we get std::is_invocable
#include <boost/iterator/transform_iterator.hpp>
#include <functional> // std::function
#include <memory> // std::unique_ptr
@@ -48,6 +47,7 @@
#include "llevents.h"
#include "llptrto.h"
#include "llsdutil.h"
+#include "stringize.h"
class LLSD;
@@ -99,7 +99,7 @@ public:
template <typename CALLABLE,
typename=typename std::enable_if<
- boost::hof::is_invocable<CALLABLE, LLSD>::value
+ std::is_invocable<CALLABLE, LLSD>::value
>::type>
void add(const std::string& name,
const std::string& desc,
@@ -201,7 +201,7 @@ public:
template <typename R, class CLASS, typename ARG,
typename = typename std::enable_if<
! std::is_same<typename std::decay<ARG>::type, LLSD>::value
- >::type>
+ >::type>
void add(const std::string& name,
const std::string& desc,
R (CLASS::*method)(ARG))
@@ -213,7 +213,7 @@ public:
template <typename R, class CLASS, typename ARG,
typename = typename std::enable_if<
! std::is_same<typename std::decay<ARG>::type, LLSD>::value
- >::type>
+ >::type>
void add(const std::string& name,
const std::string& desc,
R (CLASS::*method)(ARG) const)
@@ -226,7 +226,7 @@ public:
template <class CLASS, typename ARG,
typename = typename std::enable_if<
! std::is_same<typename std::decay<ARG>::type, LLSD>::value
- >::type>
+ >::type>
void add(const std::string& name,
const std::string& desc,
void (CLASS::*method)(ARG))
@@ -238,7 +238,7 @@ public:
template <class CLASS, typename ARG,
typename = typename std::enable_if<
! std::is_same<typename std::decay<ARG>::type, LLSD>::value
- >::type>
+ >::type>
void add(const std::string& name,
const std::string& desc,
void (CLASS::*method)(ARG) const)
@@ -247,7 +247,7 @@ public:
}
// non-const binary (or more) method
- template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS>
+ template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS>
void add(const std::string& name,
const std::string& desc,
R (CLASS::*method)(ARG0, ARG1, ARGS...))
@@ -256,7 +256,7 @@ public:
}
// const binary (or more) method
- template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS>
+ template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS>
void add(const std::string& name,
const std::string& desc,
R (CLASS::*method)(ARG0, ARG1, ARGS...) const)
@@ -265,7 +265,7 @@ public:
}
// non-const binary (or more) method returning void
- template <class CLASS, typename ARG0, typename ARG1, typename... ARGS>
+ template <class CLASS, typename ARG0, typename ARG1, typename... ARGS>
void add(const std::string& name,
const std::string& desc,
void (CLASS::*method)(ARG0, ARG1, ARGS...))
@@ -274,7 +274,7 @@ public:
}
// const binary (or more) method returning void
- template <class CLASS, typename ARG0, typename ARG1, typename... ARGS>
+ template <class CLASS, typename ARG0, typename ARG1, typename... ARGS>
void add(const std::string& name,
const std::string& desc,
void (CLASS::*method)(ARG0, ARG1, ARGS...) const)
@@ -296,7 +296,7 @@ public:
*/
template <typename CALLABLE,
typename=typename std::enable_if<
- ! boost::hof::is_invocable<CALLABLE, LLSD>()
+ ! std::is_invocable<CALLABLE, LLSD>()
>::type>
void add(const std::string& name,
const std::string& desc,
@@ -338,7 +338,7 @@ public:
template<typename Function,
typename = typename std::enable_if<
boost::function_types::is_nonmember_callable_builtin<Function>::value &&
- ! boost::hof::is_invocable<Function, LLSD>::value
+ ! std::is_invocable<Function, LLSD>::value
>::type>
void add(const std::string& name, const std::string& desc, Function f,
const LLSD& params, const LLSD& defaults=LLSD());
@@ -371,7 +371,7 @@ public:
const InstanceGetter& getter, const LLSD& params,
const LLSD& defaults=LLSD());
- //@}
+ //@}
/// Unregister a callable
bool remove(const std::string& name);
@@ -851,6 +851,8 @@ public:
ARGS&&... args);
virtual ~LLDispatchListener() {}
+ std::string getPumpName() const { return getName(); }
+
private:
bool process(const LLSD& event) const;
void call_one(const LLSD& name, const LLSD& event) const;
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 604ee8a42d..ad61e9298a 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-03-05
* @brief Implementation for lleventfilter.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -33,20 +33,19 @@
// STL headers
// std headers
// external library headers
-#include <boost/bind.hpp>
// other Linden headers
+#include "lldate.h"
#include "llerror.h" // LL_ERRS
+#include "lleventtimer.h"
#include "llsdutil.h" // llsd_matches()
#include "stringize.h"
-#include "lleventtimer.h"
-#include "lldate.h"
/*****************************************************************************
* LLEventFilter
*****************************************************************************/
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
LLEventStream(name, tweak),
- mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)))
+ mSource(source.listen(getName(), [this](const LLSD& event){ return post(event); }))
{
}
@@ -74,136 +73,52 @@ bool LLEventMatching::post(const LLSD& event)
}
/*****************************************************************************
-* LLEventTimeoutBase
+* LLEventTimeout
*****************************************************************************/
-LLEventTimeoutBase::LLEventTimeoutBase():
+LLEventTimeout::LLEventTimeout():
LLEventFilter("timeout")
{
}
-LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source):
+LLEventTimeout::LLEventTimeout(LLEventPump& source):
LLEventFilter(source, "timeout")
{
}
-void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action)
+void LLEventTimeout::actionAfter(F32 seconds, const Action& action)
{
- setCountdown(seconds);
- mAction = action;
- if (! mMainloop.connected())
- {
- LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
- mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1));
- }
+ mTimer = LL::Timers::instance().scheduleAfter(action, seconds);
}
-class ErrorAfter
+void LLEventTimeout::errorAfter(F32 seconds, const std::string& message)
{
-public:
- ErrorAfter(const std::string& message): mMessage(message) {}
-
- void operator()()
- {
- LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL;
- }
-
-private:
- std::string mMessage;
-};
-
-void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message)
-{
- actionAfter(seconds, ErrorAfter(message));
+ actionAfter(
+ seconds,
+ [message=message]
+ {
+ LL_ERRS("LLEventTimeout") << message << LL_ENDL;
+ });
}
-class EventAfter
+void LLEventTimeout::eventAfter(F32 seconds, const LLSD& event)
{
-public:
- EventAfter(LLEventPump& pump, const LLSD& event):
- mPump(pump),
- mEvent(event)
- {}
-
- void operator()()
- {
- mPump.post(mEvent);
- }
-
-private:
- LLEventPump& mPump;
- LLSD mEvent;
-};
-
-void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event)
-{
- actionAfter(seconds, EventAfter(*this, event));
+ actionAfter(seconds, [this, event]{ post(event); });
}
-bool LLEventTimeoutBase::post(const LLSD& event)
+bool LLEventTimeout::post(const LLSD& event)
{
cancel();
return LLEventStream::post(event);
}
-void LLEventTimeoutBase::cancel()
+void LLEventTimeout::cancel()
{
- mMainloop.disconnect();
+ mTimer.cancel();
}
-bool LLEventTimeoutBase::tick(const LLSD&)
+bool LLEventTimeout::running() const
{
- if (countdownElapsed())
- {
- cancel();
- mAction();
- }
- return false; // show event to other listeners
-}
-
-bool LLEventTimeoutBase::running() const
-{
- return mMainloop.connected();
-}
-
-/*****************************************************************************
-* LLEventTimeout
-*****************************************************************************/
-LLEventTimeout::LLEventTimeout() {}
-
-LLEventTimeout::LLEventTimeout(LLEventPump& source):
- LLEventTimeoutBase(source)
-{
-}
-
-void LLEventTimeout::setCountdown(F32 seconds)
-{
- mTimer.setTimerExpirySec(seconds);
-}
-
-bool LLEventTimeout::countdownElapsed() const
-{
- return mTimer.hasExpired();
-}
-
-LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data)
-{
- return LLEventTimer::run_every(
- period,
- [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
-}
-
-LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data)
-{
- return LLEventTimer::run_at(
- time,
- [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
-}
-
-LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data)
-{
- return LLEventTimer::run_after(
- interval,
- [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+ return LL::Timers::instance().isRunning(mTimer);
}
/*****************************************************************************
@@ -246,21 +161,21 @@ void LLEventBatch::setSize(std::size_t size)
}
/*****************************************************************************
-* LLEventThrottleBase
+* LLEventThrottle
*****************************************************************************/
-LLEventThrottleBase::LLEventThrottleBase(F32 interval):
+LLEventThrottle::LLEventThrottle(F32 interval):
LLEventFilter("throttle"),
mInterval(interval),
mPosts(0)
{}
-LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval):
+LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
LLEventFilter(source, "throttle"),
mInterval(interval),
mPosts(0)
{}
-void LLEventThrottleBase::flush()
+void LLEventThrottle::flush()
{
// flush() is a no-op unless there's something pending.
// Don't test mPending because there's no requirement that the consumer
@@ -281,12 +196,12 @@ void LLEventThrottleBase::flush()
}
}
-LLSD LLEventThrottleBase::pending() const
+LLSD LLEventThrottle::pending() const
{
return mPending;
}
-bool LLEventThrottleBase::post(const LLSD& event)
+bool LLEventThrottle::post(const LLSD& event)
{
// Always capture most recent post() event data. If caller wants to
// aggregate multiple events, let them retrieve pending() and modify
@@ -311,13 +226,13 @@ bool LLEventThrottleBase::post(const LLSD& event)
// timeRemaining tells us how much longer it will be until
// mInterval seconds since the last flush() call. At that time,
// flush() deferred events.
- alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ alarmActionAfter(timeRemaining, [this]{ flush(); });
}
}
return false;
}
-void LLEventThrottleBase::setInterval(F32 interval)
+void LLEventThrottle::setInterval(F32 interval)
{
F32 oldInterval = mInterval;
mInterval = interval;
@@ -349,41 +264,30 @@ void LLEventThrottleBase::setInterval(F32 interval)
// and if mAlarm is running, reset that too
if (alarmRunning())
{
- alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ alarmActionAfter(timeRemaining, [this](){ flush(); });
}
}
}
}
-F32 LLEventThrottleBase::getDelay() const
+F32 LLEventThrottle::getDelay() const
{
return timerGetRemaining();
}
-/*****************************************************************************
-* LLEventThrottle implementation
-*****************************************************************************/
-LLEventThrottle::LLEventThrottle(F32 interval):
- LLEventThrottleBase(interval)
-{}
-
-LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
- LLEventThrottleBase(source, interval)
-{}
-
-void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action)
+void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeout::Action& action)
{
- mAlarm.actionAfter(interval, action);
+ mAlarm = LL::Timers::instance().scheduleAfter(action, interval);
}
bool LLEventThrottle::alarmRunning() const
{
- return mAlarm.running();
+ return LL::Timers::instance().isRunning(mAlarm);
}
void LLEventThrottle::alarmCancel()
{
- return mAlarm.cancel();
+ LL::Timers::instance().cancel(mAlarm);
}
void LLEventThrottle::timerSet(F32 interval)
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 5c45144fad..b39791c560 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-03-05
* @brief Define LLEventFilter: LLEventStream subclass with conditions
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -29,13 +29,13 @@
#if ! defined(LL_LLEVENTFILTER_H)
#define LL_LLEVENTFILTER_H
+#include "llcallbacklist.h"
#include "llevents.h"
-#include "stdtypes.h"
-#include "lltimer.h"
#include "llsdutil.h"
-#include <boost/function.hpp>
+#include "lltimer.h"
+#include "stdtypes.h"
+#include <functional>
-class LLEventTimer;
class LLDate;
/**
@@ -78,22 +78,27 @@ private:
/**
* Wait for an event to be posted. If no such event arrives within a specified
- * time, take a specified action. See LLEventTimeout for production
- * implementation.
- *
- * @NOTE This is an abstract base class so that, for testing, we can use an
- * alternate "timer" that doesn't actually consume real time.
+ * time, take a specified action.
+ *
+ * @NOTE: Caution should be taken when using the LLEventTimeout(LLEventPump &)
+ * constructor to ensure that the upstream event pump is not an LLEventMaildrop
+ * or any other kind of store and forward pump which may have events outstanding.
+ * Using this constructor will cause the upstream event pump to fire any pending
+ * events and could result in the invocation of a virtual method before the timeout
+ * has been fully constructed. The timeout should instead be constructed separately
+ * from the event pump and attached using the listen method.
+ * See llcoro::suspendUntilEventOnWithTimeout() for an example.
*/
-class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter
+class LL_COMMON_API LLEventTimeout: public LLEventFilter
{
public:
/// construct standalone
- LLEventTimeoutBase();
+ LLEventTimeout();
/// construct and connect
- LLEventTimeoutBase(LLEventPump& source);
+ LLEventTimeout(LLEventPump& source);
/// Callable, can be constructed with boost::bind()
- typedef boost::function<void()> Action;
+ typedef std::function<void()> Action;
/**
* Start countdown timer for the specified number of @a seconds. Forward
@@ -120,8 +125,8 @@ public:
* @endcode
*
* @NOTE
- * The implementation relies on frequent events on the LLEventPump named
- * "mainloop".
+ * The implementation relies on frequent calls to
+ * gIdleCallbacks.callFunctions().
*/
void actionAfter(F32 seconds, const Action& action);
@@ -134,7 +139,7 @@ public:
* Instantiate an LLEventTimeout listening to that API and call
* errorAfter() on each async request with a timeout comfortably longer
* than the API's time guarantee (much longer than the anticipated
- * "mainloop" granularity).
+ * gIdleCallbacks.callFunctions() granularity).
*
* Then if the async API breaks its promise, the program terminates with
* the specified LL_ERRS @a message. The client of the async API can
@@ -149,11 +154,11 @@ public:
/**
* Like actionAfter(), but where the desired Action is a particular event
* for all listeners. Pass the timeout time and the desired @a event data.
- *
+ *
* Suppose the timeout should only be satisfied by a particular event, but
* the ultimate listener must see all other incoming events as well, plus
* the timeout @a event if any:
- *
+ *
* @code
* some LLEventMatching LLEventMatching
* event ---> for particular ---> LLEventTimeout ---> for timeout
@@ -161,7 +166,7 @@ public:
* \ \ ultimate
* `-----------------------------------------------------> listener
* @endcode
- *
+ *
* Since a given listener can listen on more than one LLEventPump, we can
* set things up so it sees the set union of events from LLEventTimeout
* and the original event source. However, as LLEventTimeout passes
@@ -184,55 +189,9 @@ public:
/// Is this timer currently running?
bool running() const;
-protected:
- virtual void setCountdown(F32 seconds) = 0;
- virtual bool countdownElapsed() const = 0;
-
-private:
- bool tick(const LLSD&);
-
- LLTempBoundListener mMainloop;
- Action mAction;
-};
-
-/**
- * Production implementation of LLEventTimoutBase.
- *
- * @NOTE: Caution should be taken when using the LLEventTimeout(LLEventPump &)
- * constructor to ensure that the upstream event pump is not an LLEventMaildrop
- * or any other kind of store and forward pump which may have events outstanding.
- * Using this constructor will cause the upstream event pump to fire any pending
- * events and could result in the invocation of a virtual method before the timeout
- * has been fully constructed. The timeout should instead be connected upstream
- * from the event pump and attached using the listen method.
- * See llcoro::suspendUntilEventOnWithTimeout() for an example.
- */
-
-class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase
-{
-public:
- LLEventTimeout();
- LLEventTimeout(LLEventPump& source);
-
- /// using LLEventTimeout as namespace for free functions
- /// Post event to specified LLEventPump every period seconds. Delete
- /// returned LLEventTimer* to cancel.
- static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data);
- /// Post event to specified LLEventPump at specified future time. Call
- /// LLEventTimer::getInstance(returned pointer) to check whether it's still
- /// pending; if so, delete the pointer to cancel.
- static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data);
- /// Post event to specified LLEventPump after specified interval. Call
- /// LLEventTimer::getInstance(returned pointer) to check whether it's still
- /// pending; if so, delete the pointer to cancel.
- static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data);
-
-protected:
- virtual void setCountdown(F32 seconds);
- virtual bool countdownElapsed() const;
-
private:
- LLTimer mTimer;
+ // Use a temp_handle_t so it's canceled on destruction.
+ LL::Timers::temp_handle_t mTimer;
};
/**
@@ -264,7 +223,7 @@ private:
};
/**
- * LLEventThrottleBase: construct with a time interval. Regardless of how
+ * LLEventThrottle: construct with a time interval. Regardless of how
* frequently you call post(), LLEventThrottle will pass on an event to
* its listeners no more often than once per specified interval.
*
@@ -297,13 +256,13 @@ private:
* alternate "timer" that doesn't actually consume real time. See
* LLEventThrottle.
*/
-class LL_COMMON_API LLEventThrottleBase: public LLEventFilter
+class LL_COMMON_API LLEventThrottle: public LLEventFilter
{
public:
// pass time interval
- LLEventThrottleBase(F32 interval);
+ LLEventThrottle(F32 interval);
// construct and connect
- LLEventThrottleBase(LLEventPump& source, F32 interval);
+ LLEventThrottle(LLEventPump& source, F32 interval);
// force out any deferred events
void flush();
@@ -324,45 +283,24 @@ public:
// time until next event would be passed through, 0.0 if now
F32 getDelay() const;
-protected:
- // Implement these time-related methods for a valid LLEventThrottleBase
- // subclass (see LLEventThrottle). For testing, we use a subclass that
- // doesn't involve actual elapsed time.
- virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0;
- virtual bool alarmRunning() const = 0;
- virtual void alarmCancel() = 0;
- virtual void timerSet(F32 interval) = 0;
- virtual F32 timerGetRemaining() const = 0;
-
private:
- // remember throttle interval
- F32 mInterval;
- // count post() calls since last flush()
- std::size_t mPosts;
+ void alarmActionAfter(F32 interval, const LLEventTimeout::Action& action);
+ bool alarmRunning() const;
+ void alarmCancel();
+ void timerSet(F32 interval);
+ F32 timerGetRemaining() const;
+
// pending event data from most recent deferred event
LLSD mPending;
-};
-
-/**
- * Production implementation of LLEventThrottle.
- */
-class LLEventThrottle: public LLEventThrottleBase
-{
-public:
- LLEventThrottle(F32 interval);
- LLEventThrottle(LLEventPump& source, F32 interval);
-
-private:
- virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/;
- virtual bool alarmRunning() const /*override*/;
- virtual void alarmCancel() /*override*/;
- virtual void timerSet(F32 interval) /*override*/;
- virtual F32 timerGetRemaining() const /*override*/;
-
- // use this to arrange a deferred flush() call
- LLEventTimeout mAlarm;
// use this to track whether we're within mInterval of last flush()
LLTimer mTimer;
+ // count post() calls since last flush()
+ std::size_t mPosts;
+ // remember throttle interval
+ F32 mInterval;
+
+ // use this to arrange a deferred flush() call
+ LL::Timers::handle_t mAlarm;
};
/**
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 5b4e69659d..5a6e13cb7d 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2008-09-12
* @brief Implementation for llevents.
- *
+ *
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -43,6 +43,7 @@
#include <typeinfo>
#include <cmath>
#include <cctype>
+#include <iomanip> // std::quoted
// external library headers
#include <boost/range/iterator_range.hpp>
#if LL_WINDOWS
@@ -54,10 +55,11 @@
#pragma warning (pop)
#endif
// other Linden headers
-#include "stringize.h"
#include "llerror.h"
-#include "llsdutil.h"
+#include "lleventfilter.h"
#include "llexception.h"
+#include "llsdutil.h"
+#include "stringize.h"
#if LL_MSVC
#pragma warning (disable : 4702)
#endif
@@ -71,7 +73,9 @@ LLEventPumps::LLEventPumps():
{ "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
- { return new LLEventMailDrop(name, tweak); } }
+ { return new LLEventMailDrop(name, tweak); } },
+ { "LLEventLogProxy", [](const std::string& name, bool tweak, const std::string& /*type*/)
+ { return new LLEventLogProxyFor<LLEventStream>(name, tweak); } }
},
mTypes
{
@@ -186,8 +190,13 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message)
PumpMap::iterator found = mPumpMap.find(name);
if (found == mPumpMap.end())
+ {
+ LL_DEBUGS("LLEventPumps") << "LLEventPump(" << std::quoted(name) << ") not found"
+ << LL_ENDL;
return false;
+ }
+// LL_DEBUGS("LLEventPumps") << "posting to " << name << ": " << message << LL_ENDL;
return (*found).second->post(message);
}
@@ -350,8 +359,7 @@ const std::string LLEventPump::ANONYMOUS = std::string();
LLEventPump::LLEventPump(const std::string& name, bool tweak):
// Register every new instance with LLEventPumps
- mRegistry(LLEventPumps::instance().getHandle()),
- mName(mRegistry.get()->registerNew(*this, name, tweak)),
+ mName(LLEventPumps::instance().registerNew(*this, name, tweak)),
mSignal(std::make_shared<LLStandardSignal>()),
mEnabled(true)
{}
@@ -364,10 +372,9 @@ LLEventPump::~LLEventPump()
{
// Unregister this doomed instance from LLEventPumps -- but only if
// LLEventPumps is still around!
- LLEventPumps* registry = mRegistry.get();
- if (registry)
+ if (LLEventPumps::instanceExists())
{
- registry->unregister(*this);
+ LLEventPumps::instance().unregister(*this);
}
}
@@ -414,7 +421,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
{
if (!mSignal)
{
- LL_WARNS() << "Can't connect listener" << LL_ENDL;
+ LL_WARNS("LLEventPump") << "Can't connect listener" << LL_ENDL;
// connect will fail, return dummy
return LLBoundListener();
}
@@ -423,8 +430,8 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
float nodePosition = 1.0;
- // if the supplied name is empty we are not interested in the ordering mechanism
- // and can bypass attempting to find the optimal location to insert the new
+ // if the supplied name is empty we are not interested in the ordering mechanism
+ // and can bypass attempting to find the optimal location to insert the new
// listener. We'll just tack it on to the end.
if (!name.empty()) // should be the same as testing against ANONYMOUS
{
@@ -569,12 +576,12 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
// Now that newNode has a value that places it appropriately in mSignal,
// connect it.
LLBoundListener bound = mSignal->connect(nodePosition, listener);
-
+
if (!name.empty())
{ // note that we are not tracking anonymous listeners here either.
- // This means that it is the caller's responsibility to either assign
- // to a TempBoundListerer (scoped_connection) or manually disconnect
- // when done.
+ // This means that it is the caller's responsibility to either assign
+ // to a TempBoundListerer (scoped_connection) or manually disconnect
+ // when done.
mConnections[name] = bound;
}
return bound;
@@ -641,9 +648,9 @@ bool LLEventMailDrop::post(const LLSD& event)
{
// forward the call to our base class
bool posted = LLEventStream::post(event);
-
+
if (!posted)
- { // if the event was not handled we will save it for later so that it can
+ { // if the event was not handled we will save it for later so that it can
// be posted to any future listeners when they attach.
mEventHistory.push_back(event);
}
@@ -733,7 +740,7 @@ void LLReqID::stamp(LLSD& response) const
response["reqid"] = mReqid;
}
-bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey)
+bool sendReply(LLSD reply, const LLSD& request, const std::string& replyKey)
{
// If the original request has no value for replyKey, it's pointless to
// construct or send a reply event: on which LLEventPump should we send
@@ -746,13 +753,13 @@ bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyK
// Here the request definitely contains replyKey; reasonable to proceed.
- // Copy 'reply' to modify it.
- LLSD newreply(reply);
// Get the ["reqid"] element from request
LLReqID reqID(request);
- // and copy it to 'newreply'.
- reqID.stamp(newreply);
- // Send reply on LLEventPump named in request[replyKey]. Don't forget to
- // send the modified 'newreply' instead of the original 'reply'.
- return LLEventPumps::instance().obtain(request[replyKey]).post(newreply);
+ // and copy it to 'reply'.
+ reqID.stamp(reply);
+ // Send reply on LLEventPump named in request[replyKey] -- if that
+ // LLEventPump exists. If it does not, don't create it.
+ // This addresses the case in which a requester goes away before a
+ // particular LLEventAPI responds.
+ return LLEventPumps::instance().post(request[replyKey], reply);
}
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index 9a0a6863f0..2cc1f91408 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -6,25 +6,25 @@
* https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System,
* originally introduced in llnotifications.h. It has nothing
* whatsoever to do with the older system in llevent.h.
- *
+ *
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -39,35 +39,21 @@
#include <deque>
#include <functional>
#if LL_WINDOWS
- #pragma warning (push)
- #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
- #pragma warning (disable : 4264)
+ #pragma warning (push)
+ #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
+ #pragma warning (disable : 4264)
#endif
#include <boost/signals2.hpp>
#if LL_WINDOWS
- #pragma warning (pop)
+ #pragma warning (pop)
#endif
-#include <boost/bind.hpp>
-#include <boost/utility.hpp> // noncopyable
#include <boost/optional/optional.hpp>
-#include <boost/visit_each.hpp>
-#include <boost/ref.hpp> // reference_wrapper
-#include <boost/type_traits/is_pointer.hpp>
-#include <boost/static_assert.hpp>
-#include "llsd.h"
-#include "llsingleton.h"
#include "lldependencies.h"
-#include "llstl.h"
#include "llexception.h"
-#include "llhandle.h"
-
-/*==========================================================================*|
-// override this to allow binding free functions with more parameters
-#ifndef LLEVENTS_LISTENER_ARITY
-#define LLEVENTS_LISTENER_ARITY 10
-#endif
-|*==========================================================================*/
+#include "llmutex.h"
+#include "llsd.h"
+#include "llsingleton.h"
// hack for testing
#ifndef testable
@@ -151,6 +137,8 @@ typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float> LL
/// Methods that forward listeners (e.g. constructed with
/// <tt>boost::bind()</tt>) should accept (const LLEventListener&)
typedef LLStandardSignal::slot_type LLEventListener;
+/// Accept a void listener too
+typedef std::function<void(const LLSD&)> LLVoidListener;
/// Result of registering a listener, supports <tt>connected()</tt>,
/// <tt>disconnect()</tt> and <tt>blocked()</tt>
typedef boost::signals2::connection LLBoundListener;
@@ -225,15 +213,7 @@ class LLEventPump;
* LLEventPumps is a Singleton manager through which one typically accesses
* this subsystem.
*/
-// LLEventPumps isa LLHandleProvider only for (hopefully rare) long-lived
-// class objects that must refer to this class late in their lifespan, say in
-// the destructor. Specifically, the case that matters is a possible reference
-// after LLEventPumps::deleteSingleton(). (Lingering LLEventPump instances are
-// capable of this.) In that case, instead of calling LLEventPumps::instance()
-// again -- resurrecting the deleted LLSingleton -- store an
-// LLHandle<LLEventPumps> and test it before use.
-class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps>,
- public LLHandleProvider<LLEventPumps>
+class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps>
{
LLSINGLETON(LLEventPumps);
public:
@@ -310,9 +290,9 @@ public:
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
- *
+ *
* returns the result of the LLEventPump::post. If no pump exists returns false.
- *
+ *
* This is syntactically similar to LLEventPumps::instance().post(name, message),
* however if the pump does not already exist it will not be created.
*/
@@ -541,10 +521,10 @@ 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
+ *
+ * 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.
*/
@@ -594,12 +574,7 @@ private:
virtual void clear();
virtual void reset();
-
-
private:
- // must precede mName; see LLEventPump::LLEventPump()
- LLHandle<LLEventPumps> mRegistry;
-
std::string mName;
LLMutex mConnectionListMutex;
@@ -607,7 +582,7 @@ protected:
virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
const NameList& after,
const NameList& before);
-
+
/// implement the dispatching
std::shared_ptr<LLStandardSignal> mSignal;
@@ -660,21 +635,21 @@ public:
* by all listeners, until some listener consumes it. The caveat is that each
* event *must* eventually reach a listener that will consume it, else the
* queue will grow to arbitrary length.
- *
+ *
* @NOTE: When using an LLEventMailDrop with an LLEventTimeout or
* LLEventFilter attaching the filter downstream, using Timeout's constructor will
- * cause the MailDrop to discharge any of its stored events. The timeout should
- * instead be connected upstream using its listen() method.
+ * cause the MailDrop to discharge any of its stored events. The timeout should
+ * instead be connected upstream using its listen() method.
*/
class LL_COMMON_API LLEventMailDrop : public LLEventStream
{
public:
LLEventMailDrop(const std::string& name, bool tweak = false) : LLEventStream(name, tweak) {}
virtual ~LLEventMailDrop() {}
-
+
/// Post an event to all listeners
virtual bool post(const LLSD& event) override;
-
+
/// Remove any history stored in the mail drop.
void discard();
@@ -689,6 +664,30 @@ private:
};
/*****************************************************************************
+* LLNamedListener
+*****************************************************************************/
+/**
+ * LLNamedListener bundles a concrete LLEventPump subclass with a specific
+ * listener function, with an LLTempBoundListener to ensure that it's
+ * disconnected before destruction.
+ */
+template <class PUMP=LLEventStream>
+class LL_COMMON_API LLNamedListener: PUMP
+{
+ using pump_t = PUMP;
+public:
+ template <typename LISTENER>
+ LLNamedListener(const std::string& name, LISTENER&& listener):
+ pump_t(name, false), // don't tweak the name
+ mConn(pump_t::listen("func", std::forward<LISTENER>(listener)))
+ {}
+
+private:
+ LLTempBoundListener mConn;
+};
+using LLStreamListener = LLNamedListener<>;
+
+/*****************************************************************************
* LLReqID
*****************************************************************************/
/**
@@ -780,7 +779,7 @@ private:
* Before sending the reply event, sendReply() copies the ["reqid"] item from
* the request to the reply.
*/
-LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
+LL_COMMON_API bool sendReply(LLSD reply, const LLSD& request,
const std::string& replyKey="reply");
#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp
index 33fafffefd..43c1a255ac 100644
--- a/indra/llcommon/lleventtimer.cpp
+++ b/indra/llcommon/lleventtimer.cpp
@@ -1,73 +1,68 @@
-/**
+/**
* @file lleventtimer.cpp
- * @brief Cross-platform objects for doing timing
+ * @brief Cross-platform objects for doing timing
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
-
#include "lleventtimer.h"
-#include "u64.h"
-
-
//////////////////////////////////////////////////////////////////////////////
//
-// LLEventTimer Implementation
+// LLEventTimer Implementation
//
//////////////////////////////////////////////////////////////////////////////
-LLEventTimer::LLEventTimer(F32 period)
-: mEventTimer()
+LLEventTimer::LLEventTimer(F32 period):
+ mPeriod(period)
{
- mPeriod = period;
+ start();
}
-LLEventTimer::LLEventTimer(const LLDate& time)
-: mEventTimer()
+LLEventTimer::LLEventTimer(const LLDate& time):
+ LLEventTimer(F32(time.secondsSinceEpoch() - LLDate::now().secondsSinceEpoch()))
+{}
+
+LLEventTimer::~LLEventTimer()
{
- mPeriod = (F32)(time.secondsSinceEpoch() - LLDate::now().secondsSinceEpoch());
}
-
-LLEventTimer::~LLEventTimer()
+void LLEventTimer::start()
{
+ mTimer = LL::Timers::instance().scheduleEvery([this]{ return tick(); }, mPeriod);
}
-//static
-void LLEventTimer::updateClass()
+void LLEventTimer::stop()
{
- for (auto& timer : instance_snapshot())
- {
- F32 et = timer.mEventTimer.getElapsedTimeF32();
- if (timer.mEventTimer.getStarted() && et > timer.mPeriod) {
- timer.mEventTimer.reset();
- if ( timer.tick() )
- {
- delete &timer;
- }
- }
- }
+ LL::Timers::instance().cancel(mTimer);
}
+bool LLEventTimer::isRunning()
+{
+ return LL::Timers::instance().isRunning(mTimer);
+}
+F32 LLEventTimer::getRemaining()
+{
+ return LL::Timers::instance().timeUntilCall(mTimer);
+}
diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h
index ed6f10d5e1..a325c704e0 100644
--- a/indra/llcommon/lleventtimer.h
+++ b/indra/llcommon/lleventtimer.h
@@ -1,25 +1,25 @@
-/**
+/**
* @file lleventtimer.h
- * @brief Cross-platform objects for doing timing
+ * @brief Cross-platform objects for doing timing
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -27,96 +27,31 @@
#ifndef LL_EVENTTIMER_H
#define LL_EVENTTIMER_H
-#include "stdtypes.h"
+#include "llcallbacklist.h"
#include "lldate.h"
-#include "llinstancetracker.h"
-#include "lltimer.h"
+#include "stdtypes.h"
// class for scheduling a function to be called at a given frequency (approximate, inprecise)
-class LL_COMMON_API LLEventTimer : public LLInstanceTracker<LLEventTimer>
+class LL_COMMON_API LLEventTimer
{
public:
- LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
- LLEventTimer(const LLDate& time);
- virtual ~LLEventTimer();
+ LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
+ LLEventTimer(const LLDate& time);
+ virtual ~LLEventTimer();
- //function to be called at the supplied frequency
- // Normally return FALSE; TRUE will delete the timer after the function returns.
- virtual BOOL tick() = 0;
+ void start();
+ void stop();
+ bool isRunning();
+ F32 getRemaining();
- static void updateClass();
-
- /// Schedule recurring calls to generic callable every period seconds.
- /// Returns a pointer; if you delete it, cancels the recurring calls.
- template <typename CALLABLE>
- static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
-
- /// Schedule a future call to generic callable. Returns a pointer.
- /// CAUTION: The object referenced by that pointer WILL BE DELETED once
- /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
- /// pointer->getInstance(pointer)!) can be used to test whether the
- /// pointer is still valid. If it is, deleting it will cancel the
- /// callback.
- template <typename CALLABLE>
- static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
-
- /// Like run_at(), but after a time delta rather than at a timestamp.
- /// Same CAUTION.
- template <typename CALLABLE>
- static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
+ //function to be called at the supplied frequency
+ // Normally return false; true will delete the timer after the function returns.
+ virtual bool tick() = 0;
protected:
- LLTimer mEventTimer;
- F32 mPeriod;
-
-private:
- template <typename CALLABLE>
- class Generic;
+ LL::Timers::temp_handle_t mTimer;
+ F32 mPeriod;
};
-template <typename CALLABLE>
-class LLEventTimer::Generic: public LLEventTimer
-{
-public:
- // making TIME generic allows engaging either LLEventTimer constructor
- template <typename TIME>
- Generic(const TIME& time, bool once, const CALLABLE& callable):
- LLEventTimer(time),
- mOnce(once),
- mCallable(callable)
- {}
- BOOL tick() override
- {
- mCallable();
- // true tells updateClass() to delete this instance
- return mOnce;
- }
-
-private:
- bool mOnce;
- CALLABLE mCallable;
-};
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
-{
- // return false to schedule recurring calls
- return new Generic<CALLABLE>(period, false, callable);
-}
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
-{
- // return true for one-shot callback
- return new Generic<CALLABLE>(time, true, callable);
-}
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
-{
- // one-shot callback after specified interval
- return new Generic<CALLABLE>(interval, true, callable);
-}
-
#endif //LL_EVENTTIMER_H
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
index 68e609444e..9e322db86d 100644
--- a/indra/llcommon/llexception.h
+++ b/indra/llcommon/llexception.h
@@ -3,7 +3,7 @@
* @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$
@@ -12,6 +12,7 @@
#if ! defined(LL_LLEXCEPTION_H)
#define LL_LLEXCEPTION_H
+#include "stdtypes.h"
#include <stdexcept>
#include <boost/exception/exception.hpp>
#include <boost/throw_exception.hpp>
diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h
index 4456a72696..97ea3b7b78 100644
--- a/indra/llcommon/llformat.h
+++ b/indra/llcommon/llformat.h
@@ -1,4 +1,4 @@
-/**
+/**
* @file llformat.h
* @date January 2007
* @brief string formatting utility
@@ -6,21 +6,21 @@
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -28,6 +28,8 @@
#ifndef LL_LLFORMAT_H
#define LL_LLFORMAT_H
+#include "llpreprocessor.h"
+
// Use as follows:
// LL_INFOS() << llformat("Test:%d (%.2f %.2f)", idx, x, y) << LL_ENDL;
//
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 3232a0e219..921f743ada 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -1,4 +1,4 @@
-/**
+/**
* @file llinstancetracker.h
* @brief LLInstanceTracker is a mixin class that automatically tracks object
* instances with or without an associated key
@@ -6,21 +6,21 @@
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -99,11 +99,11 @@ public:
return mSelf;
}
- static size_t instanceCount()
- {
- return LockStatic()->mMap.size();
+ static size_t instanceCount()
+ {
+ return LockStatic()->mMap.size();
}
-
+
// snapshot of std::pair<const KEY, std::shared_ptr<SUBCLASS>> pairs, for
// some SUBCLASS derived from T
template <typename SUBCLASS>
@@ -243,7 +243,7 @@ public:
}
protected:
- LLInstanceTracker(const KEY& key)
+ LLInstanceTracker(const KEY& key)
{
// We do not intend to manage the lifespan of this object with
// shared_ptr, so give it a no-op deleter. We store shared_ptrs in our
@@ -275,6 +275,35 @@ protected:
public:
virtual const KEY& getKey() const { return mInstanceKey; }
+ /// for use ONLY for an object we're sure resides on the heap!
+ static bool destruct(const KEY& key)
+ {
+ return destruct(getInstance(key));
+ }
+
+ /// for use ONLY for an object we're sure resides on the heap!
+ static bool destruct(const weak_t& ptr)
+ {
+ return destruct(ptr.lock());
+ }
+
+ /// for use ONLY for an object we're sure resides on the heap!
+ static bool destruct(const ptr_t& ptr)
+ {
+ if (! ptr)
+ {
+ return false;
+ }
+
+ // Because we store and return ptr_t instances with no-op deleters,
+ // merely resetting the last pointer doesn't destroy the referenced
+ // object. Don't even bother resetting 'ptr'. Just extract its raw
+ // pointer and delete that.
+ auto raw{ ptr.get() };
+ delete raw;
+ return true;
+ }
+
private:
LLInstanceTracker( const LLInstanceTracker& ) = delete;
LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete;
@@ -286,9 +315,9 @@ private:
static std::string report(const char* key) { return report(std::string(key)); }
// caller must instantiate LockStatic
- void add_(LockStatic& lock, const KEY& key, const ptr_t& ptr)
- {
- mInstanceKey = key;
+ void add_(LockStatic& lock, const KEY& key, const ptr_t& ptr)
+ {
+ mInstanceKey = key;
InstanceMap& map = lock->mMap;
switch(KEY_COLLISION_BEHAVIOR)
{
@@ -373,7 +402,7 @@ public:
{
return mSelf;
}
-
+
static size_t instanceCount()
{
return LockStatic()->mSet.size();
@@ -479,6 +508,29 @@ public:
template <typename SUBCLASS>
using key_snapshot_of = instance_snapshot_of<SUBCLASS>;
+ /// for use ONLY for an object we're sure resides on the heap!
+ static bool destruct(const weak_t& ptr)
+ {
+ return destruct(ptr.lock());
+ }
+
+ /// for use ONLY for an object we're sure resides on the heap!
+ static bool destruct(const ptr_t& ptr)
+ {
+ if (! ptr)
+ {
+ return false;
+ }
+
+ // Because we store and return ptr_t instances with no-op deleters,
+ // merely resetting the last pointer doesn't destroy the referenced
+ // object. Don't even bother resetting 'ptr'. Just extract its raw
+ // pointer and delete that.
+ auto raw{ ptr.get() };
+ delete raw;
+ return true;
+ }
+
protected:
LLInstanceTracker()
{
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index d0fb586459..f7f2981638 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2012-02-20
* @brief Implementation for llleap.
- *
+ *
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (c) 2012, Linden Research, Inc.
* $/LicenseInfo$
@@ -14,13 +14,11 @@
// associated header
#include "llleap.h"
// STL headers
-#include <sstream>
#include <algorithm>
+#include <memory>
+#include <sstream>
// std headers
// external library headers
-#include <boost/bind.hpp>
-#include <boost/scoped_ptr.hpp>
-#include <boost/tokenizer.hpp>
// other Linden headers
#include "llerror.h"
#include "llstring.h"
@@ -53,18 +51,19 @@ public:
// We expect multiple LLLeapImpl instances. Definitely tweak
// mDonePump's name for uniqueness.
mDonePump("LLLeap", true),
- // Troubling thought: what if one plugin intentionally messes with
- // another plugin? LLEventPump names are in a single global namespace.
- // Try to make that more difficult by generating a UUID for the reply-
- // pump name -- so it should NOT need tweaking for uniqueness.
- mReplyPump(LLUUID::generateNewID().asString()),
mExpect(0),
// Instantiate a distinct LLLeapListener for this plugin. (Every
// plugin will want its own collection of managed listeners, etc.)
- // Pass it a callback to our connect() method, so it can send events
+ // Pass it our wstdin() method as its callback, so it can send events
// from a particular LLEventPump to the plugin without having to know
// this class or method name.
- mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))
+ mListener(
+ new LLLeapListener(
+ "LLLeap",
+ // Serialize any reply event to our child's stdin, suitably
+ // enriched with the pump name on which it was received.
+ [this](const std::string& pump, const LLSD& data)
+ { return wstdin(pump, data); }))
{
// Rule out unpopulated Params block
if (! cparams.executable.isProvided())
@@ -93,7 +92,7 @@ public:
}
// Listen for child "termination" right away to catch launch errors.
- mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));
+ mDonePump.listen("LLLeap", [this](const LLSD& data){ return bad_launch(data); });
// Okay, launch child.
// Get a modifiable copy of params block to set files and postend.
@@ -113,7 +112,7 @@ public:
// Okay, launch apparently worked. Change our mDonePump listener.
mDonePump.stopListening("LLLeap");
- mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1));
+ mDonePump.listen("LLLeap", [this](const LLSD& data){ return done(data); });
// Child might pump large volumes of data through either stdout or
// stderr. Don't bother copying all that data into notification event.
@@ -123,17 +122,14 @@ public:
childout.setLimit(20);
childerr.setLimit(20);
- // Serialize any event received on mReplyPump to our child's stdin.
- mStdinConnection = connect(mReplyPump, "LLLeap");
-
// Listening on stdout is stateful. In general, we're either waiting
// for the length prefix or waiting for the specified length of data.
// We address that with two different listener methods -- one of which
// is blocked at any given time.
mStdoutConnection = childout.getPump()
- .listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1));
+ .listen("prefix", [this](const LLSD& data){ return rstdout(data); });
mStdoutDataConnection = childout.getPump()
- .listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1));
+ .listen("data", [this](const LLSD& data){ return rstdoutData(data); });
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
// Log anything sent up through stderr. When a typical program
@@ -142,7 +138,7 @@ public:
// interpreter behaves that way. More generally, though, a plugin
// author can log whatever s/he wants to the viewer log using stderr.
mStderrConnection = childerr.getPump()
- .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1));
+ .listen("LLLeap", [this](const LLSD& data){ return rstderr(data); });
// For our lifespan, intercept any LL_ERRS so we can notify plugin
mRecorder = LLError::addGenericRecorder(
@@ -151,7 +147,7 @@ public:
// Send child a preliminary event reporting our own reply-pump name --
// which would otherwise be pretty tricky to guess!
- wstdin(mReplyPump.getName(),
+ wstdin(mListener->getReplyPump().getName(),
LLSDMap
("command", mListener->getName())
// Include LLLeap features -- this may be important for child to
@@ -198,7 +194,7 @@ public:
return false;
}
- // Listener for events on mReplyPump: send to child stdin
+ // Listener for reply events: send to child stdin
bool wstdin(const std::string& pump, const LLSD& data)
{
LLSD packet(LLSDMap("pump", pump)("data", data));
@@ -355,7 +351,7 @@ public:
// request, send a reply. We happen to know who originated
// this request, and the reply LLEventPump of interest.
// Not our problem if the plugin ignores the reply event.
- data["reply"] = mReplyPump.getName();
+ data["reply"] = mListener->getReplyPump().getName();
sendReply(llsd::map("error",
stringize(LLError::Log::classname(err), ": ", err.what())),
data);
@@ -428,7 +424,7 @@ public:
LLSD event;
event["type"] = "error";
event["error"] = error;
- mReplyPump.post(event);
+ mListener->getReplyPump().post(event);
// All the above really accomplished was to buffer the serialized
// event in our WritePipe. Have to pump mainloop a couple times to
@@ -445,23 +441,10 @@ public:
}
private:
- /// We always want to listen on mReplyPump with wstdin(); under some
- /// circumstances we'll also echo other LLEventPumps to the plugin.
- LLBoundListener connect(LLEventPump& pump, const std::string& listener)
- {
- // Serialize any event received on the specified LLEventPump to our
- // child's stdin, suitably enriched with the pump name on which it was
- // received.
- return pump.listen(listener,
- boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1));
- }
-
std::string mDesc;
LLEventStream mDonePump;
- LLEventStream mReplyPump;
LLProcessPtr mChild;
- LLTempBoundListener
- mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection;
+ LLTempBoundListener mStdoutConnection, mStdoutDataConnection, mStderrConnection;
std::unique_ptr<LLEventPump::Blocker> mBlocker;
LLProcess::ReadPipe::size_type mExpect;
LLError::RecorderPtr mRecorder;
diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
index 050d71c327..9b9b0f5121 100644
--- a/indra/llcommon/llleaplistener.cpp
+++ b/indra/llcommon/llleaplistener.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2012-03-16
* @brief Implementation for llleaplistener.
- *
+ *
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (c) 2012, Linden Research, Inc.
* $/LicenseInfo$
@@ -54,53 +54,62 @@
return features;
}
-LLLeapListener::LLLeapListener(const ConnectFunc& connect):
+LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& callback):
// Each LEAP plugin has an instance of this listener. Make the command
// pump name difficult for other such plugins to guess.
LLEventAPI(LLUUID::generateNewID().asString(),
"Operations relating to the LLSD Event API Plugin (LEAP) protocol"),
- mConnect(connect)
+ mCaller(caller),
+ mCallback(callback),
+ // Troubling thought: what if one plugin intentionally messes with
+ // another plugin? LLEventPump names are in a single global namespace.
+ // Try to make that more difficult by generating a UUID for the reply-
+ // pump name -- so it should NOT need tweaking for uniqueness.
+ mReplyPump(LLUUID::generateNewID().asString()),
+ mReplyConn(connect(mReplyPump, mCaller))
{
LLSD need_name(LLSDMap("name", LLSD()));
add("newpump",
- "Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
- "[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n"
- "Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
- "Returns actual name in [\"name\"] (may be different if collision).",
+R"-(Instantiate a new LLEventPump named like ["name"] and listen to it.
+["type"] == "LLEventStream", "LLEventMailDrop" et al.
+Events sent through new LLEventPump will be decorated with ["pump"]=name.
+Returns actual name in ["name"] (may be different if collision).)-",
&LLLeapListener::newpump,
need_name);
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
add("listen",
- "Listen to an existing LLEventPump named [\"source\"], with listener name\n"
- "[\"listener\"].\n"
- "By default, send events on [\"source\"] to the plugin, decorated\n"
- "with [\"pump\"]=[\"source\"].\n"
- "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n"
- "LLEventPump named [\"dest\"].\n"
- "Returns [\"status\"] boolean indicating whether the connection was made.",
+R"-(Listen to an existing LLEventPump named ["source"], with listener name
+["listener"].
+If ["tweak"] is specified as true, tweak listener name for uniqueness.
+By default, send events on ["source"] to the plugin, decorated
+with ["pump"]=["source"].
+If ["dest"] specified, send undecorated events on ["source"] to the
+LLEventPump named ["dest"].
+Returns ["status"] boolean indicating whether the connection was made,
+plus ["listener"] reporting (possibly tweaked) listener name.)-",
&LLLeapListener::listen,
need_source_listener);
add("stoplistening",
- "Disconnect a connection previously established by \"listen\".\n"
- "Pass same [\"source\"] and [\"listener\"] arguments.\n"
- "Returns [\"status\"] boolean indicating whether such a listener existed.",
+R"-(Disconnect a connection previously established by "listen".
+Pass same ["source"] and ["listener"] arguments.
+Returns ["status"] boolean indicating whether such a listener existed.)-",
&LLLeapListener::stoplistening,
need_source_listener);
add("ping",
- "No arguments, just a round-trip sanity check.",
+"No arguments, just a round-trip sanity check.",
&LLLeapListener::ping);
add("getAPIs",
- "Enumerate all LLEventAPI instances by name and description.",
+"Enumerate all LLEventAPI instances by name and description.",
&LLLeapListener::getAPIs);
add("getAPI",
- "Get name, description, dispatch key and operations for LLEventAPI [\"api\"].",
+R"-(Get name, description, dispatch key and operations for LLEventAPI ["api"].)-",
&LLLeapListener::getAPI,
LLSD().with("api", LLSD()));
add("getFeatures",
- "Return an LLSD map of feature strings (deltas from baseline LEAP protocol)",
+"Return an LLSD map of feature strings (deltas from baseline LEAP protocol)",
static_cast<void (LLLeapListener::*)(const LLSD&) const>(&LLLeapListener::getFeatures));
add("getFeature",
- "Return the feature value with key [\"feature\"]",
+R"-(Return the feature value with key ["feature"])-",
&LLLeapListener::getFeature,
LLSD().with("feature", LLSD()));
}
@@ -112,6 +121,7 @@ LLLeapListener::~LLLeapListener()
// value_type, and Bad Things would happen if you copied an
// LLTempBoundListener. (Destruction of the original would disconnect the
// listener, invalidating every stored connection.)
+ LL_DEBUGS("LLLeapListener") << "~LLLeapListener(\"" << mCaller << "\")" << LL_ENDL;
for (ListenersMap::value_type& pair : mListeners)
{
pair.second.disconnect();
@@ -133,8 +143,7 @@ void LLLeapListener::newpump(const LLSD& request)
reply["name"] = name;
// Now listen on this new pump with our plugin listener
- std::string myname("llleap");
- saveListener(name, myname, mConnect(new_pump, myname));
+ saveListener(name, mCaller, connect(new_pump, mCaller));
}
catch (const LLEventPumps::BadType& error)
{
@@ -149,6 +158,11 @@ void LLLeapListener::listen(const LLSD& request)
std::string source_name = request["source"];
std::string dest_name = request["dest"];
std::string listener_name = request["listener"];
+ if (request["tweak"].asBoolean())
+ {
+ listener_name = LLEventPump::inventName(listener_name);
+ }
+ reply["listener"] = listener_name;
LLEventPump & source = LLEventPumps::instance().obtain(source_name);
@@ -170,7 +184,7 @@ void LLLeapListener::listen(const LLSD& request)
{
// "dest" unspecified means to direct events on "source"
// to our plugin listener.
- saveListener(source_name, listener_name, mConnect(source, listener_name));
+ saveListener(source_name, listener_name, connect(source, listener_name));
}
reply["status"] = true;
}
@@ -292,10 +306,27 @@ void LLLeapListener::getFeature(const LLSD& request) const
}
}
+LLBoundListener LLLeapListener::connect(LLEventPump& pump, const std::string& listener)
+{
+ // Connect to source pump with an adapter that calls our callback with the
+ // pump name as well as the event data.
+ return pump.listen(
+ listener,
+ [callback=mCallback, pump=pump.getName()]
+ (const LLSD& data)
+ { return callback(pump, data); });
+}
+
void LLLeapListener::saveListener(const std::string& pump_name,
const std::string& listener_name,
const LLBoundListener& listener)
{
- mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name),
- listener));
+ // Don't use insert() or emplace() because, if this (pump_name,
+ // listener_name) pair is already in mListeners, we *want* to overwrite it.
+ auto& listener_entry{ mListeners[ListenersMap::key_type(pump_name, listener_name)] };
+ // If we already stored a connection for this pump and listener name,
+ // disconnect it before overwriting it. But if this entry was newly
+ // created, disconnect() will be a no-op.
+ listener_entry.disconnect();
+ listener_entry = listener;
}
diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h
index cad4543d02..040fb737b7 100644
--- a/indra/llcommon/llleaplistener.h
+++ b/indra/llcommon/llleaplistener.h
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2012-03-16
* @brief LLEventAPI supporting LEAP plugins
- *
+ *
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (c) 2012, Linden Research, Inc.
* $/LicenseInfo$
@@ -13,10 +13,9 @@
#define LL_LLLEAPLISTENER_H
#include "lleventapi.h"
+#include <functional>
#include <map>
#include <string>
-#include <boost/function.hpp>
-#include <boost/ptr_container/ptr_map.hpp>
/// Listener class implementing LLLeap query/control operations.
/// See https://jira.lindenlab.com/jira/browse/DEV-31978.
@@ -24,18 +23,16 @@ class LLLeapListener: public LLEventAPI
{
public:
/**
- * Decouple LLLeap by dependency injection. Certain LLLeapListener
- * operations must be able to cause LLLeap to listen on a specified
- * LLEventPump with the LLLeap listener that wraps incoming events in an
- * outer (pump=, data=) map and forwards them to the plugin. Very well,
- * define the signature for a function that will perform that, and make
- * our constructor accept such a function.
+ * Certain LLLeapListener operations listen on a specified LLEventPump.
+ * Accept a bool(pump, data) callback from our caller for when any such
+ * event is received.
*/
- typedef boost::function<LLBoundListener(LLEventPump&, const std::string& listener)>
- ConnectFunc;
- LLLeapListener(const ConnectFunc& connect);
+ using Callback = std::function<bool(const std::string& pump, const LLSD& data)>;
+ LLLeapListener(const std::string_view& caller, const Callback& callback);
~LLLeapListener();
+ LLEventPump& getReplyPump() { return mReplyPump; }
+
static LLSD getFeatures();
private:
@@ -48,10 +45,16 @@ private:
void getFeatures(const LLSD&) const;
void getFeature(const LLSD&) const;
+ LLBoundListener connect(LLEventPump& pump, const std::string& listener);
void saveListener(const std::string& pump_name, const std::string& listener_name,
const LLBoundListener& listener);
- ConnectFunc mConnect;
+ // The relative order of these next declarations is important because the
+ // constructor will initialize in this order.
+ std::string mCaller;
+ Callback mCallback;
+ LLEventStream mReplyPump;
+ LLTempBoundListener mReplyConn;
// In theory, listen() could simply call the relevant LLEventPump's
// listen() method, stoplistening() likewise. Lifespan issues make us
diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp
index 15651a6813..692a21c1f1 100644
--- a/indra/llcommon/lllivefile.cpp
+++ b/indra/llcommon/lllivefile.cpp
@@ -1,24 +1,24 @@
-/**
+/**
* @file lllivefile.cpp
*
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -35,51 +35,51 @@ const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f;
class LLLiveFile::Impl
{
public:
- Impl(const std::string& filename, const F32 refresh_period);
- ~Impl();
-
- bool check();
- void changed();
-
- bool mForceCheck;
- F32 mRefreshPeriod;
- LLFrameTimer mRefreshTimer;
-
- std::string mFilename;
- time_t mLastModTime;
- time_t mLastStatTime;
- bool mLastExists;
-
- LLEventTimer* mEventTimer;
+ Impl(const std::string& filename, const F32 refresh_period);
+ ~Impl();
+
+ bool check();
+ void changed();
+
+ bool mForceCheck;
+ F32 mRefreshPeriod;
+ LLFrameTimer mRefreshTimer;
+
+ std::string mFilename;
+ time_t mLastModTime;
+ time_t mLastStatTime;
+ bool mLastExists;
+
+ LLEventTimer* mEventTimer;
private:
LOG_CLASS(LLLiveFile);
};
LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period)
- :
- mForceCheck(true),
- mRefreshPeriod(refresh_period),
- mFilename(filename),
- mLastModTime(0),
- mLastStatTime(0),
- mLastExists(false),
- mEventTimer(NULL)
+ :
+ mForceCheck(true),
+ mRefreshPeriod(refresh_period),
+ mFilename(filename),
+ mLastModTime(0),
+ mLastStatTime(0),
+ mLastExists(false),
+ mEventTimer(NULL)
{
}
LLLiveFile::Impl::~Impl()
{
- delete mEventTimer;
+ delete mEventTimer;
}
LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period)
- : impl(* new Impl(filename, refresh_period))
+ : impl(* new Impl(filename, refresh_period))
{
}
LLLiveFile::~LLLiveFile()
{
- delete &impl;
+ delete &impl;
}
@@ -88,8 +88,8 @@ bool LLLiveFile::Impl::check()
bool detected_change = false;
// Skip the check if not enough time has elapsed and we're not
// forcing a check of the file
- if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod)
- {
+ if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod)
+ {
mForceCheck = false; // force only forces one check
mRefreshTimer.reset(); // don't check again until mRefreshPeriod has passed
@@ -98,11 +98,11 @@ bool LLLiveFile::Impl::check()
if (LLFile::stat(mFilename, &stat_data))
{
// Couldn't stat the file, that means it doesn't exist or is
- // broken somehow.
+ // broken somehow.
if (mLastExists)
{
mLastExists = false;
- detected_change = true; // no longer existing is a change!
+ detected_change = true; // no longer existing is a change!
LL_DEBUGS() << "detected deleted file '" << mFilename << "'" << LL_ENDL;
}
}
@@ -134,65 +134,65 @@ bool LLLiveFile::Impl::check()
void LLLiveFile::Impl::changed()
{
- // we wanted to read this file, and we were successful.
- mLastModTime = mLastStatTime;
+ // we wanted to read this file, and we were successful.
+ mLastModTime = mLastStatTime;
}
bool LLLiveFile::checkAndReload()
{
- bool changed = impl.check();
- if (changed)
- {
- if(loadFile())
- {
- impl.changed();
- this->changed();
- }
- else
- {
- changed = false;
- }
- }
- return changed;
+ bool changed = impl.check();
+ if (changed)
+ {
+ if(loadFile())
+ {
+ impl.changed();
+ this->changed();
+ }
+ else
+ {
+ changed = false;
+ }
+ }
+ return changed;
}
std::string LLLiveFile::filename() const
{
- return impl.mFilename;
+ return impl.mFilename;
}
namespace
{
- class LiveFileEventTimer : public LLEventTimer
- {
- public:
- LiveFileEventTimer(LLLiveFile& f, F32 refresh)
- : LLEventTimer(refresh), mLiveFile(f)
- { }
-
- BOOL tick()
- {
- mLiveFile.checkAndReload();
- return FALSE;
- }
-
- private:
- LLLiveFile& mLiveFile;
- };
-
+ class LiveFileEventTimer : public LLEventTimer
+ {
+ public:
+ LiveFileEventTimer(LLLiveFile& f, F32 refresh)
+ : LLEventTimer(refresh), mLiveFile(f)
+ { }
+
+ bool tick() override
+ {
+ mLiveFile.checkAndReload();
+ return FALSE;
+ }
+
+ private:
+ LLLiveFile& mLiveFile;
+ };
+
}
void LLLiveFile::addToEventTimer()
{
- impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod);
+ impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod);
}
void LLLiveFile::setRefreshPeriod(F32 seconds)
{
- if (seconds < 0.f)
- {
- seconds = -seconds;
- }
- impl.mRefreshPeriod = seconds;
+ if (seconds < 0.f)
+ {
+ seconds = -seconds;
+ }
+ impl.mRefreshPeriod = seconds;
}
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h
index 28ad62830b..dde6c20210 100644
--- a/indra/llcommon/llmainthreadtask.h
+++ b/indra/llcommon/llmainthreadtask.h
@@ -4,7 +4,7 @@
* @date 2019-12-04
* @brief LLMainThreadTask dispatches work to the main thread. When invoked on
* the main thread, it performs the work inline.
- *
+ *
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
* Copyright (c) 2019, Linden Research, Inc.
* $/LicenseInfo$
@@ -13,11 +13,8 @@
#if ! defined(LL_LLMAINTHREADTASK_H)
#define LL_LLMAINTHREADTASK_H
-#include "lleventtimer.h"
#include "llthread.h"
-#include "llmake.h"
-#include <future>
-#include <type_traits> // std::result_of
+#include "workqueue.h"
/**
* LLMainThreadTask provides a way to perform some task specifically on the
@@ -28,18 +25,17 @@
* Instead of instantiating LLMainThreadTask, pass your invocable to its
* static dispatch() method. dispatch() returns the result of calling your
* task. (Or, if your task throws an exception, dispatch() throws that
- * exception. See std::packaged_task.)
+ * exception.)
*
* When you call dispatch() on the main thread (as determined by
* on_main_thread() in llthread.h), it simply calls your task and returns the
* result.
*
- * When you call dispatch() on a secondary thread, it instantiates an
- * LLEventTimer subclass scheduled immediately. Next time the main loop calls
- * LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask
- * will fulfill a future with its result. Meanwhile the requesting thread
- * blocks on that future. As soon as it is set, the requesting thread wakes up
- * with the task result.
+ * When you call dispatch() on a secondary thread, it posts your task to
+ * gMainloopWork, the WorkQueue serviced by the main thread, using
+ * WorkQueue::waitForResult() to block the caller. Next time the main loop
+ * calls gMainloopWork.runFor(), your task will be run, and waitForResult()
+ * will return its result.
*/
class LLMainThreadTask
{
@@ -59,41 +55,15 @@ public:
}
else
{
- // It's essential to construct LLEventTimer subclass instances on
- // the heap because, on completion, LLEventTimer deletes them.
- // Once we enable C++17, we can use Class Template Argument
- // Deduction. Until then, use llmake_heap().
- auto* task = llmake_heap<Task>(std::forward<CALLABLE>(callable));
- auto future = task->mTask.get_future();
- // Now simply block on the future.
- return future.get();
+ auto queue{ LL::WorkQueue::getInstance("mainloop") };
+ // If this needs a null check and a message, please introduce a
+ // method in the .cpp file so consumers of this header don't drag
+ // in llerror.h.
+ // Use waitForResult_() so dispatch() can be used even from the
+ // calling thread's default coroutine.
+ return queue->waitForResult_(std::forward<CALLABLE>(callable));
}
}
-
-private:
- template <typename CALLABLE>
- struct Task: public LLEventTimer
- {
- Task(CALLABLE&& callable):
- // no wait time: call tick() next chance we get
- LLEventTimer(0),
- mTask(std::forward<CALLABLE>(callable))
- {}
- BOOL tick() override
- {
- // run the task on the main thread, will populate the future
- // obtained by get_future()
- mTask();
- // tell LLEventTimer we're done (one shot)
- return TRUE;
- }
- // Given arbitrary CALLABLE, which might be a lambda, how are we
- // supposed to obtain its signature for std::packaged_task? It seems
- // redundant to have to add an argument list to engage result_of, then
- // add the argument list again to complete the signature. At least we
- // only support a nullary CALLABLE.
- std::packaged_task<typename std::result_of<CALLABLE()>::type()> mTask;
- };
};
#endif /* ! defined(LL_LLMAINTHREADTASK_H) */
diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h
index 33c9e956b1..5789b5de2c 100644
--- a/indra/llcommon/llrefcount.h
+++ b/indra/llcommon/llrefcount.h
@@ -1,25 +1,25 @@
-/**
+/**
* @file llrefcount.h
* @brief Base class for reference counted objects for use with LLPointer
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -29,6 +29,7 @@
#include <boost/noncopyable.hpp>
#include <boost/intrusive_ptr.hpp>
#include "llatomic.h"
+#include "llerror.h"
class LLMutex;
@@ -44,42 +45,42 @@ extern const S32 gMaxRefCount;
class LL_COMMON_API LLRefCount
{
protected:
- LLRefCount(const LLRefCount& other);
- LLRefCount& operator=(const LLRefCount&);
- virtual ~LLRefCount(); // use unref()
-
+ LLRefCount(const LLRefCount& other);
+ LLRefCount& operator=(const LLRefCount&);
+ virtual ~LLRefCount(); // use unref()
+
public:
- LLRefCount();
-
- inline void ref() const
- {
- llassert(mRef != LL_REFCOUNT_FREE); // object is deleted
- mRef++;
- llassert(mRef < gMaxRefCount); // ref count excessive, likely memory leak
- }
-
- inline S32 unref() const
- {
- llassert(mRef != LL_REFCOUNT_FREE); // object is deleted
- llassert(mRef > 0); // ref count below 1, likely corrupted
- if (0 == --mRef)
- {
- mRef = LL_REFCOUNT_FREE; // set to nonsense yet recognizable value to aid in debugging
- delete this;
- return 0;
- }
- return mRef;
- }
-
- //NOTE: when passing around a const LLRefCount object, this can return different results
- // at different types, since mRef is mutable
- S32 getNumRefs() const
- {
- return mRef;
- }
+ LLRefCount();
+
+ inline void ref() const
+ {
+ llassert(mRef != LL_REFCOUNT_FREE); // object is deleted
+ mRef++;
+ llassert(mRef < gMaxRefCount); // ref count excessive, likely memory leak
+ }
+
+ inline S32 unref() const
+ {
+ llassert(mRef != LL_REFCOUNT_FREE); // object is deleted
+ llassert(mRef > 0); // ref count below 1, likely corrupted
+ if (0 == --mRef)
+ {
+ mRef = LL_REFCOUNT_FREE; // set to nonsense yet recognizable value to aid in debugging
+ delete this;
+ return 0;
+ }
+ return mRef;
+ }
+
+ //NOTE: when passing around a const LLRefCount object, this can return different results
+ // at different types, since mRef is mutable
+ S32 getNumRefs() const
+ {
+ return mRef;
+ }
private:
- mutable S32 mRef;
+ mutable S32 mRef;
};
@@ -90,50 +91,50 @@ private:
class LL_COMMON_API LLThreadSafeRefCount
{
public:
- static void initThreadSafeRefCount(); // creates sMutex
- static void cleanupThreadSafeRefCount(); // destroys sMutex
+ static void initThreadSafeRefCount(); // creates sMutex
+ static void cleanupThreadSafeRefCount(); // destroys sMutex
private:
- static LLMutex* sMutex;
+ static LLMutex* sMutex;
protected:
- virtual ~LLThreadSafeRefCount(); // use unref()
+ virtual ~LLThreadSafeRefCount(); // use unref()
public:
- LLThreadSafeRefCount();
- LLThreadSafeRefCount(const LLThreadSafeRefCount&);
- LLThreadSafeRefCount& operator=(const LLThreadSafeRefCount& ref)
- {
- mRef = 0;
- return *this;
- }
-
- void ref()
- {
- mRef++;
- }
-
- void unref()
- {
- llassert(mRef >= 1);
- if ((--mRef) == 0)
- {
- // If we hit zero, the caller should be the only smart pointer owning the object and we can delete it.
- // It is technically possible for a vanilla pointer to mess this up, or another thread to
- // jump in, find this object, create another smart pointer and end up dangling, but if
- // the code is that bad and not thread-safe, it's trouble already.
- delete this;
- }
- }
-
- S32 getNumRefs() const
- {
- const S32 currentVal = mRef.CurrentValue();
- return currentVal;
- }
+ LLThreadSafeRefCount();
+ LLThreadSafeRefCount(const LLThreadSafeRefCount&);
+ LLThreadSafeRefCount& operator=(const LLThreadSafeRefCount& ref)
+ {
+ mRef = 0;
+ return *this;
+ }
+
+ void ref()
+ {
+ mRef++;
+ }
+
+ void unref()
+ {
+ llassert(mRef >= 1);
+ if ((--mRef) == 0)
+ {
+ // If we hit zero, the caller should be the only smart pointer owning the object and we can delete it.
+ // It is technically possible for a vanilla pointer to mess this up, or another thread to
+ // jump in, find this object, create another smart pointer and end up dangling, but if
+ // the code is that bad and not thread-safe, it's trouble already.
+ delete this;
+ }
+ }
+
+ S32 getNumRefs() const
+ {
+ const S32 currentVal = mRef.CurrentValue();
+ return currentVal;
+ }
private:
- LLAtomicS32 mRef;
+ LLAtomicS32 mRef;
};
/**
@@ -142,12 +143,12 @@ private:
*/
inline void intrusive_ptr_add_ref(LLThreadSafeRefCount* p)
{
- p->ref();
+ p->ref();
}
inline void intrusive_ptr_release(LLThreadSafeRefCount* p)
{
- p->unref();
+ p->unref();
}
/**
@@ -156,12 +157,12 @@ inline void intrusive_ptr_release(LLThreadSafeRefCount* p)
*/
inline void intrusive_ptr_add_ref(LLRefCount* p)
{
- p->ref();
+ p->ref();
}
inline void intrusive_ptr_release(LLRefCount* p)
{
- p->unref();
+ p->unref();
}
#endif
diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h
index 8061117ad5..d85a704376 100644
--- a/indra/llcommon/llrun.h
+++ b/indra/llcommon/llrun.h
@@ -1,4 +1,4 @@
-/**
+/**
* @file llrun.h
* @author Phoenix
* @date 2006-02-16
@@ -7,21 +7,21 @@
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -34,7 +34,18 @@
class LLRunnable;
-/**
+//////////////////////////////////////////////////////////////////////////////
+// DEPRECATION WARNING
+// LLRunner is one of several mostly redundant ways to schedule future
+// callbacks on the main thread. It seems to be unused in the current viewer.
+// addRunnable() is only called by LLPumpIO::sleepChain().
+// sleepChain() is only called by LLIOSleeper and LLIOSleep.
+// LLIOSleeper is referenced only by tests.
+// LLIOSleep is only called by LLDeferredChain.
+// LLDeferredChain isn't referenced at all.
+//////////////////////////////////////////////////////////////////////////////
+
+/**
* @class LLRunner
* @brief This class manages a set of LLRunnable objects.
*
@@ -45,96 +56,96 @@ class LLRunnable;
class LL_COMMON_API LLRunner
{
public:
- /**
- * @brief The pointer to a runnable.
- */
- typedef std::shared_ptr<LLRunnable> run_ptr_t;
-
- /**
- * @brief The handle for use in the API.
- */
- typedef S64 run_handle_t;
-
- /**
- * @brief Constructor.
- */
- LLRunner();
-
- /**
- * @brief Destructor.
- */
- ~LLRunner();
-
- /**
- * @brief Enumeration which specifies when to run.
- */
- enum ERunSchedule
- {
- // The runnable will run in N seconds
- RUN_IN,
-
- // The run every N seconds
- RUN_EVERY,
-
- // A count of the run types
- RUN_SCHEDULE_COUNT
- };
-
- /**
- * @brief Run the runnables which are scheduled to run
- *
- * @return Returns the number of runnables run.
- */
- size_t run();
-
- /**
- * @brief Add a runnable to the run list.
- *
- * The handle of the runnable is unique to each addition. If the
- * same runnable is added a second time with the same or different
- * schedule, this method will return a new handle.
- * @param runnable The runnable to run() on schedule.
- * @param schedule Specifies the run schedule.
- * @param seconds When to run the runnable as interpreted by schedule.
- * @return Returns the handle to the runnable. handle == 0 means failure.
- */
- run_handle_t addRunnable(
- run_ptr_t runnable,
- ERunSchedule schedule,
- F64 seconds);
-
- /**
- * @brief Remove the specified runnable.
- *
- * @param handle The handle of the runnable to remove.
- * @return Returns the pointer to the runnable removed which may
- * be empty.
- */
- run_ptr_t removeRunnable(run_handle_t handle);
+ /**
+ * @brief The pointer to a runnable.
+ */
+ typedef std::shared_ptr<LLRunnable> run_ptr_t;
+
+ /**
+ * @brief The handle for use in the API.
+ */
+ typedef S64 run_handle_t;
+
+ /**
+ * @brief Constructor.
+ */
+ LLRunner();
+
+ /**
+ * @brief Destructor.
+ */
+ ~LLRunner();
+
+ /**
+ * @brief Enumeration which specifies when to run.
+ */
+ enum ERunSchedule
+ {
+ // The runnable will run in N seconds
+ RUN_IN,
+
+ // The run every N seconds
+ RUN_EVERY,
+
+ // A count of the run types
+ RUN_SCHEDULE_COUNT
+ };
+
+ /**
+ * @brief Run the runnables which are scheduled to run
+ *
+ * @return Returns the number of runnables run.
+ */
+ size_t run();
+
+ /**
+ * @brief Add a runnable to the run list.
+ *
+ * The handle of the runnable is unique to each addition. If the
+ * same runnable is added a second time with the same or different
+ * schedule, this method will return a new handle.
+ * @param runnable The runnable to run() on schedule.
+ * @param schedule Specifies the run schedule.
+ * @param seconds When to run the runnable as interpreted by schedule.
+ * @return Returns the handle to the runnable. handle == 0 means failure.
+ */
+ run_handle_t addRunnable(
+ run_ptr_t runnable,
+ ERunSchedule schedule,
+ F64 seconds);
+
+ /**
+ * @brief Remove the specified runnable.
+ *
+ * @param handle The handle of the runnable to remove.
+ * @return Returns the pointer to the runnable removed which may
+ * be empty.
+ */
+ run_ptr_t removeRunnable(run_handle_t handle);
protected:
- struct LLRunInfo
- {
- run_handle_t mHandle;
- run_ptr_t mRunnable;
- ERunSchedule mSchedule;
- F64 mNextRunAt;
- F64 mIncrement;
- LLRunInfo(
- run_handle_t handle,
- run_ptr_t runnable,
- ERunSchedule schedule,
- F64 next_run_at,
- F64 increment);
- };
- typedef std::vector<LLRunInfo> run_list_t;
- run_list_t mRunOnce;
- run_list_t mRunEvery;
- run_handle_t mNextHandle;
+ struct LLRunInfo
+ {
+ run_handle_t mHandle;
+ run_ptr_t mRunnable;
+ ERunSchedule mSchedule;
+ F64 mNextRunAt;
+ F64 mIncrement;
+ LLRunInfo(
+ run_handle_t handle,
+ run_ptr_t runnable,
+ ERunSchedule schedule,
+ F64 next_run_at,
+ F64 increment);
+ };
+ typedef std::vector<LLRunInfo> run_list_t;
+ run_list_t mRunOnce;
+ run_list_t mRunEvery;
+ run_handle_t mNextHandle;
};
-/**
+/**
* @class LLRunnable
* @brief Abstract base class for running some scheduled process.
*
@@ -146,17 +157,17 @@ protected:
class LL_COMMON_API LLRunnable
{
public:
- LLRunnable();
- virtual ~LLRunnable();
-
- /**
- * @brief Do the process.
- *
- * This method will be called from the LLRunner according to
- * @param runner The Runner which call run().
- * @param handle The handle this run instance is run under.
- */
- virtual void run(LLRunner* runner, S64 handle) = 0;
+ LLRunnable();
+ virtual ~LLRunnable();
+
+ /**
+ * @brief Do the process.
+ *
+ * This method will be called from the LLRunner according to
+ * @param runner The Runner which call run().
+ * @param handle The handle this run instance is run under.
+ */
+ virtual void run(LLRunner* runner, S64 handle) = 0;
};
#endif // LL_LLRUN_H
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index d00e703a10..0acf9513d8 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -1,25 +1,25 @@
-/**
+/**
* @file llsingleton.cpp
* @author Brad Kittenbrink
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -27,11 +27,12 @@
#include "linden_common.h"
#include "llsingleton.h"
+#include "llcoros.h"
+#include "lldependencies.h"
#include "llerror.h"
#include "llerrorcontrol.h"
-#include "lldependencies.h"
#include "llexception.h"
-#include "llcoros.h"
+#include "llmainthreadtask.h"
#include <algorithm>
#include <iostream> // std::cerr in dire emergency
#include <sstream>
@@ -363,7 +364,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
// should happen basically once: for deleteAll().
typedef LLDependencies<LLSingletonBase*> SingletonDeps;
SingletonDeps sdeps;
- // Lock while traversing the master list
+ // Lock while traversing the master list
MasterList::LockedMaster master;
for (LLSingletonBase* sp : master.get())
{
@@ -455,7 +456,7 @@ std::ostream& operator<<(std::ostream& out, const LLSingletonBase::string_params
return out;
}
-} // anonymous namespace
+} // anonymous namespace
//static
void LLSingletonBase::logwarns(const string_params& args)
@@ -485,3 +486,29 @@ std::string LLSingletonBase::demangle(const char* mangled)
{
return LLError::Log::demangle(mangled);
}
+
+LLSingletonBase* LLSingletonBase::getInstanceForSecondaryThread(
+ const std::string& name,
+ const std::string& method,
+ const std::function<LLSingletonBase*()>& getInstance)
+{
+ // Normally it would be the height of folly to reference-bind args into a
+ // lambda to be executed on some other thread! By the time that thread
+ // executed the lambda, the references would all be dangling, and Bad
+ // Things would result. But LLMainThreadTask::dispatch() promises to block
+ // the calling thread until the passed task has completed. So in this case
+ // we know the references will remain valid until the lambda has run, so
+ // we dare to bind references.
+ return LLMainThreadTask::dispatch(
+ [&name, &method, &getInstance](){
+ // VERY IMPORTANT to call getInstance() on the main thread,
+ // rather than going straight to constructSingleton()!
+ // During the time window before mInitState is INITIALIZED,
+ // multiple requests might be queued. It's essential that, as
+ // the main thread processes them, only the FIRST such request
+ // actually constructs the instance -- every subsequent one
+ // simply returns the existing instance.
+ loginfos({name, "::", method, " on main thread"});
+ return getInstance();
+ });
+}
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 91c05bd5ed..25afccccc0 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -1,40 +1,42 @@
-/**
+/**
* @file llsingleton.h
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LLSINGLETON_H
#define LLSINGLETON_H
+#include <boost/call_traits.hpp>
#include <boost/noncopyable.hpp>
#include <boost/unordered_set.hpp>
+#include <functional>
#include <initializer_list>
#include <list>
#include <typeinfo>
#include <vector>
#include "mutex.h"
#include "lockstatic.h"
+#include "apply.h"
#include "llthread.h" // on_main_thread()
-#include "llmainthreadtask.h"
class LLSingletonBase: private boost::noncopyable
{
@@ -134,6 +136,17 @@ protected:
// internal wrapper around calls to cleanupSingleton()
void cleanup_();
+ // This method is where we dispatch to LLMainThreadTask to acquire the
+ // subclass LLSingleton instance when the first getInstance() call is from
+ // a secondary thread. We delegate to the .cpp file to untangle header
+ // circularity. It accepts a std::function referencing the subclass
+ // getInstance() method -- which can't be virtual because it's static; we
+ // don't yet have an instance! For messaging, it also accepts the name of
+ // the subclass and the subclass method.
+ static LLSingletonBase* getInstanceForSecondaryThread(
+ const std::string& name, const std::string& method,
+ const std::function<LLSingletonBase*()>& getInstance);
+
// 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
@@ -555,19 +568,11 @@ public:
// Per the comment block above, dispatch to the main thread.
loginfos({classname<DERIVED_TYPE>(),
"::getInstance() dispatching to main thread"});
- auto instance = LLMainThreadTask::dispatch(
- [](){
- // VERY IMPORTANT to call getInstance() on the main thread,
- // rather than going straight to constructSingleton()!
- // During the time window before mInitState is INITIALIZED,
- // multiple requests might be queued. It's essential that, as
- // the main thread processes them, only the FIRST such request
- // actually constructs the instance -- every subsequent one
- // simply returns the existing instance.
- loginfos({classname<DERIVED_TYPE>(),
- "::getInstance() on main thread"});
- return getInstance();
- });
+ auto instance = static_cast<DERIVED_TYPE*>(
+ getInstanceForSecondaryThread(
+ classname<DERIVED_TYPE>(),
+ "getInstance()",
+ getInstance));
// record the dependency chain tracked on THIS thread, not the main
// thread (consider a getInstance() overload with a tag param that
// suppresses dep tracking when dispatched to the main thread)
@@ -632,8 +637,14 @@ private:
// Passes arguments to DERIVED_TYPE's constructor and sets appropriate
// states, returning a pointer to the new instance.
+ // If we just let initParamSingleton_() infer its argument types, the
+ // compiler has trouble passing int and string literals. Use
+ // boost::call_traits::param_type to smooth parameter passing. This
+ // construction requires, though, that each invocation of this method
+ // explicitly specify template arguments, instead of inferring them.
template <typename... Args>
- static DERIVED_TYPE* initParamSingleton_(Args&&... args)
+ static LLSingletonBase* initParamSingleton_(
+ typename boost::call_traits<Args>::param_type... args)
{
// In case racing threads both call initParamSingleton() at the same
// time, serialize them. One should initialize; the other should see
@@ -652,7 +663,7 @@ private:
// on the main thread, simply construct instance while holding lock
super::logdebugs({super::template classname<DERIVED_TYPE>(),
"::initParamSingleton()"});
- super::constructSingleton(lk, std::forward<Args>(args)...);
+ super::constructSingleton(lk, args...);
return lk->mInstance;
}
else
@@ -665,20 +676,19 @@ private:
lk.unlock();
super::loginfos({super::template classname<DERIVED_TYPE>(),
"::initParamSingleton() dispatching to main thread"});
- // Normally it would be the height of folly to reference-bind
- // 'args' into a lambda to be executed on some other thread! By
- // the time that thread executed the lambda, the references would
- // all be dangling, and Bad Things would result. But
- // LLMainThreadTask::dispatch() promises to block until the passed
- // task has completed. So in this case we know the references will
- // remain valid until the lambda has run, so we dare to bind
- // references.
- auto instance = LLMainThreadTask::dispatch(
- [&](){
- super::loginfos({super::template classname<DERIVED_TYPE>(),
- "::initParamSingleton() on main thread"});
- return initParamSingleton_(std::forward<Args>(args)...);
- });
+ auto instance = static_cast<DERIVED_TYPE*>(
+ super::getInstanceForSecondaryThread(
+ super::template classname<DERIVED_TYPE>(),
+ "initParamSingleton()",
+ // This lambda does what std::bind() is supposed to do --
+ // but when the actual parameter is (e.g.) a string
+ // literal, type inference makes it fail. Apply param_type
+ // to each incoming type to make it work.
+ [args=std::tuple<typename boost::call_traits<Args>::param_type...>(args...)]
+ ()
+ {
+ return LL::apply(initParamSingleton_<Args...>, args);
+ }));
super::loginfos({super::template classname<DERIVED_TYPE>(),
"::initParamSingleton() returning on requesting thread"});
return instance;
@@ -695,7 +705,7 @@ public:
template <typename... Args>
static DERIVED_TYPE& initParamSingleton(Args&&... args)
{
- return *initParamSingleton_(std::forward<Args>(args)...);
+ return *static_cast<DERIVED_TYPE*>(initParamSingleton_<Args...>(args...));
}
static DERIVED_TYPE* getInstance()
@@ -841,7 +851,7 @@ private: \
// Relatively unsafe singleton implementation that is much faster
// and simpler than LLSingleton, but has no dependency tracking
-// or inherent thread safety and requires manual invocation of
+// or inherent thread safety and requires manual invocation of
// createInstance before first use.
template<class T>
class LLSimpleton
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index 3829978ddf..9478dff871 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -1,25 +1,25 @@
-/**
+/**
* @file llstring.h
* @brief String utility functions and std::string class.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -37,7 +37,9 @@
#include <algorithm>
#include <vector>
#include <map>
+#include <type_traits>
#include "llformat.h"
+#include "stdtypes.h"
#if LL_LINUX
#include <wctype.h>
@@ -59,85 +61,85 @@ namespace std
template<>
struct char_traits<U16>
{
- typedef U16 char_type;
- typedef int int_type;
- typedef streampos pos_type;
- typedef streamoff off_type;
- typedef mbstate_t state_type;
-
- static void
- assign(char_type& __c1, const char_type& __c2)
- { __c1 = __c2; }
-
- static bool
- eq(const char_type& __c1, const char_type& __c2)
- { return __c1 == __c2; }
-
- static bool
- lt(const char_type& __c1, const char_type& __c2)
- { return __c1 < __c2; }
-
- static int
- compare(const char_type* __s1, const char_type* __s2, size_t __n)
- { return memcmp(__s1, __s2, __n * sizeof(char_type)); }
-
- static size_t
- length(const char_type* __s)
- {
- const char_type *cur_char = __s;
- while (*cur_char != 0)
- {
- ++cur_char;
- }
- return cur_char - __s;
- }
-
- static const char_type*
- find(const char_type* __s, size_t __n, const char_type& __a)
- { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); }
-
- static char_type*
- move(char_type* __s1, const char_type* __s2, size_t __n)
- { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); }
-
- static char_type*
- copy(char_type* __s1, const char_type* __s2, size_t __n)
- { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */
-
- static char_type*
- assign(char_type* __s, size_t __n, char_type __a)
- {
- // This isn't right.
- //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type)));
-
- // I don't think there's a standard 'memset' for 16-bit values.
- // Do this the old-fashioned way.
-
- size_t __i;
- for(__i = 0; __i < __n; __i++)
- {
- __s[__i] = __a;
- }
- return __s;
- }
-
- static char_type
- to_char_type(const int_type& __c)
- { return static_cast<char_type>(__c); }
-
- static int_type
- to_int_type(const char_type& __c)
- { return static_cast<int_type>(__c); }
-
- static bool
- eq_int_type(const int_type& __c1, const int_type& __c2)
- { return __c1 == __c2; }
-
- static int_type
- eof() { return static_cast<int_type>(EOF); }
-
- static int_type
- not_eof(const int_type& __c)
+ typedef U16 char_type;
+ typedef int int_type;
+ typedef streampos pos_type;
+ typedef streamoff off_type;
+ typedef mbstate_t state_type;
+
+ static void
+ assign(char_type& __c1, const char_type& __c2)
+ { __c1 = __c2; }
+
+ static bool
+ eq(const char_type& __c1, const char_type& __c2)
+ { return __c1 == __c2; }
+
+ static bool
+ lt(const char_type& __c1, const char_type& __c2)
+ { return __c1 < __c2; }
+
+ static int
+ compare(const char_type* __s1, const char_type* __s2, size_t __n)
+ { return memcmp(__s1, __s2, __n * sizeof(char_type)); }
+
+ static size_t
+ length(const char_type* __s)
+ {
+ const char_type *cur_char = __s;
+ while (*cur_char != 0)
+ {
+ ++cur_char;
+ }
+ return cur_char - __s;
+ }
+
+ static const char_type*
+ find(const char_type* __s, size_t __n, const char_type& __a)
+ { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); }
+
+ static char_type*
+ move(char_type* __s1, const char_type* __s2, size_t __n)
+ { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); }
+
+ static char_type*
+ copy(char_type* __s1, const char_type* __s2, size_t __n)
+ { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */
+
+ static char_type*
+ assign(char_type* __s, size_t __n, char_type __a)
+ {
+ // This isn't right.
+ //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type)));
+
+ // I don't think there's a standard 'memset' for 16-bit values.
+ // Do this the old-fashioned way.
+
+ size_t __i;
+ for(__i = 0; __i < __n; __i++)
+ {
+ __s[__i] = __a;
+ }
+ return __s;
+ }
+
+ static char_type
+ to_char_type(const int_type& __c)
+ { return static_cast<char_type>(__c); }
+
+ static int_type
+ to_int_type(const char_type& __c)
+ { return static_cast<int_type>(__c); }
+
+ static bool
+ eq_int_type(const int_type& __c1, const int_type& __c2)
+ { return __c1 == __c2; }
+
+ static int_type
+ eof() { return static_cast<int_type>(EOF); }
+
+ static int_type
+ not_eof(const int_type& __c)
{ return (__c == eof()) ? 0 : __c; }
};
};
@@ -146,72 +148,72 @@ struct char_traits<U16>
class LL_COMMON_API LLStringOps
{
private:
- static long sPacificTimeOffset;
- static long sLocalTimeOffset;
- static bool sPacificDaylightTime;
+ static long sPacificTimeOffset;
+ static long sLocalTimeOffset;
+ static bool sPacificDaylightTime;
- static std::map<std::string, std::string> datetimeToCodes;
+ static std::map<std::string, std::string> datetimeToCodes;
public:
- static std::vector<std::string> sWeekDayList;
- static std::vector<std::string> sWeekDayShortList;
- static std::vector<std::string> sMonthList;
- static std::vector<std::string> sMonthShortList;
- static std::string sDayFormat;
-
- static std::string sAM;
- static std::string sPM;
+ static std::vector<std::string> sWeekDayList;
+ static std::vector<std::string> sWeekDayShortList;
+ static std::vector<std::string> sMonthList;
+ static std::vector<std::string> sMonthShortList;
+ static std::string sDayFormat;
- static char toUpper(char elem) { return toupper((unsigned char)elem); }
- static llwchar toUpper(llwchar elem) { return towupper(elem); }
+ static std::string sAM;
+ static std::string sPM;
- static char toLower(char elem) { return tolower((unsigned char)elem); }
- static llwchar toLower(llwchar elem) { return towlower(elem); }
+ static char toUpper(char elem) { return toupper((unsigned char)elem); }
+ static llwchar toUpper(llwchar elem) { return towupper(elem); }
+
+ static char toLower(char elem) { return tolower((unsigned char)elem); }
+ static llwchar toLower(llwchar elem) { return towlower(elem); }
- static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; }
- static bool isSpace(llwchar elem) { return iswspace(elem) != 0; }
+ static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; }
+ static bool isSpace(llwchar elem) { return iswspace(elem) != 0; }
- static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; }
- static bool isUpper(llwchar elem) { return iswupper(elem) != 0; }
+ static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; }
+ static bool isUpper(llwchar elem) { return iswupper(elem) != 0; }
- static bool isLower(char elem) { return islower((unsigned char)elem) != 0; }
- static bool isLower(llwchar elem) { return iswlower(elem) != 0; }
+ static bool isLower(char elem) { return islower((unsigned char)elem) != 0; }
+ static bool isLower(llwchar elem) { return iswlower(elem) != 0; }
- static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; }
- static bool isDigit(llwchar a) { return iswdigit(a) != 0; }
+ static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; }
+ static bool isDigit(llwchar a) { return iswdigit(a) != 0; }
- static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; }
- static bool isPunct(llwchar a) { return iswpunct(a) != 0; }
+ static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; }
+ static bool isPunct(llwchar a) { return iswpunct(a) != 0; }
- static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; }
- static bool isAlpha(llwchar a) { return iswalpha(a) != 0; }
+ static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; }
+ static bool isAlpha(llwchar a) { return iswalpha(a) != 0; }
- static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
- static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
+ static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
+ static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
- static bool isEmoji(llwchar wch);
+ static bool isEmoji(llwchar wch);
- static S32 collate(const char* a, const char* b) { return strcoll(a, b); }
- static S32 collate(const llwchar* a, const llwchar* b);
+ static S32 collate(const char* a, const char* b) { return strcoll(a, b); }
+ static S32 collate(const llwchar* a, const llwchar* b);
- static void setupDatetimeInfo(bool pacific_daylight_time);
+ static void setupDatetimeInfo(bool pacific_daylight_time);
- static void setupWeekDaysNames(const std::string& data);
- static void setupWeekDaysShortNames(const std::string& data);
- static void setupMonthNames(const std::string& data);
- static void setupMonthShortNames(const std::string& data);
- static void setupDayFormat(const std::string& data);
+ static void setupWeekDaysNames(const std::string& data);
+ static void setupWeekDaysShortNames(const std::string& data);
+ static void setupMonthNames(const std::string& data);
+ static void setupMonthShortNames(const std::string& data);
+ static void setupDayFormat(const std::string& data);
- static long getPacificTimeOffset(void) { return sPacificTimeOffset;}
- static long getLocalTimeOffset(void) { return sLocalTimeOffset;}
- // Is the Pacific time zone (aka server time zone)
- // currently in daylight savings time?
- static bool getPacificDaylightTime(void) { return sPacificDaylightTime;}
+ static long getPacificTimeOffset(void) { return sPacificTimeOffset;}
+ static long getLocalTimeOffset(void) { return sLocalTimeOffset;}
+ // Is the Pacific time zone (aka server time zone)
+ // currently in daylight savings time?
+ static bool getPacificDaylightTime(void) { return sPacificDaylightTime;}
- static std::string getDatetimeCode (std::string key);
+ static std::string getDatetimeCode (std::string key);
- // Express a value like 1234567 as "1.23M"
+ // Express a value like 1234567 as "1.23M"
static std::string getReadableNumber(F64 num);
};
@@ -228,216 +230,216 @@ LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen);
class LLFormatMapString
{
public:
- LLFormatMapString() {};
- LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {};
- LLFormatMapString(const std::string& s) : mString(s) {};
- operator std::string() const { return mString; }
- bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; }
- std::size_t length() const { return mString.length(); }
-
+ LLFormatMapString() {};
+ LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {};
+ LLFormatMapString(const std::string& s) : mString(s) {};
+ operator std::string() const { return mString; }
+ bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; }
+ std::size_t length() const { return mString.length(); }
+
private:
- std::string mString;
+ std::string mString;
};
template <class T>
class LLStringUtilBase
{
private:
- static std::string sLocale;
+ static std::string sLocale;
public:
- typedef std::basic_string<T> string_type;
- typedef typename string_type::size_type size_type;
-
+ typedef std::basic_string<T> string_type;
+ typedef typename string_type::size_type size_type;
+
public:
- /////////////////////////////////////////////////////////////////////////////////////////
- // Static Utility functions that operate on std::strings
-
- static const string_type null;
-
- typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
- /// considers any sequence of delims as a single field separator
- LL_COMMON_API static void getTokens(const string_type& instr,
- std::vector<string_type >& tokens,
- const string_type& delims);
- /// like simple scan overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& delims);
- /// add support for keep_delims and quotes (either could be empty string)
- static void getTokens(const string_type& instr,
- std::vector<string_type>& tokens,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes=string_type());
- /// like keep_delims-and-quotes overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes=string_type());
- /// add support for escapes (could be empty string)
- static void getTokens(const string_type& instr,
- std::vector<string_type>& tokens,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes);
- /// like escapes overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes);
-
- LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
- LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
- LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
- LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
- LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
- LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
- LL_COMMON_API static void setLocale (std::string inLocale);
- LL_COMMON_API static std::string getLocale (void);
-
- static bool isValidIndex(const string_type& string, size_type i)
- {
- return !string.empty() && (0 <= i) && (i <= string.size());
- }
-
- static bool contains(const string_type& string, T c, size_type i=0)
- {
- return string.find(c, i) != string_type::npos;
- }
-
- static void trimHead(string_type& string);
- static void trimTail(string_type& string);
- static void trim(string_type& string) { trimHead(string); trimTail(string); }
- static void truncate(string_type& string, size_type count);
-
- static void toUpper(string_type& string);
- static void toLower(string_type& string);
-
- // True if this is the head of s.
- static BOOL isHead( const string_type& string, const T* s );
-
- /**
- * @brief Returns true if string starts with substr
- *
- * If etither string or substr are empty, this method returns false.
- */
- static bool startsWith(
- const string_type& string,
- const string_type& substr);
-
- /**
- * @brief Returns true if string ends in substr
- *
- * If etither string or substr are empty, this method returns false.
- */
- static bool endsWith(
- const string_type& string,
- const string_type& substr);
-
- /**
- * get environment string value with proper Unicode handling
- * (key is always UTF-8)
- * detect absence by return value == dflt
- */
- static string_type getenv(const std::string& key, const string_type& dflt="");
- /**
- * get optional environment string value with proper Unicode handling
- * (key is always UTF-8)
- * detect absence by (! return value)
- */
- static boost::optional<string_type> getoptenv(const std::string& key);
-
- static void addCRLF(string_type& string);
- static void removeCRLF(string_type& string);
- static void removeWindowsCR(string_type& string);
-
- static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
- static void replaceNonstandardASCII( string_type& string, T replacement );
- static void replaceChar( string_type& string, T target, T replacement );
- static void replaceString( string_type& string, string_type target, string_type replacement );
- static string_type capitalize(const string_type& str);
- static void capitalize(string_type& str);
-
- static BOOL containsNonprintable(const string_type& string);
- static void stripNonprintable(string_type& string);
-
- /**
- * Double-quote an argument string if needed, unless it's already
- * double-quoted. Decide whether it's needed based on the presence of any
- * character in @a triggers (default space or double-quote). If we quote
- * it, escape any embedded double-quote with the @a escape string (default
- * backslash).
- *
- * Passing triggers="" means always quote, unless it's already double-quoted.
- */
- static string_type quote(const string_type& str,
- const string_type& triggers=" \"",
- const string_type& escape="\\");
-
- /**
- * @brief Unsafe way to make ascii characters. You should probably
- * only call this when interacting with the host operating system.
- * The 1 byte std::string does not work correctly.
- * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
- * should work.
- */
- static void _makeASCII(string_type& string);
-
- // Conversion to other data types
- static BOOL convertToBOOL(const string_type& string, BOOL& value);
- static BOOL convertToU8(const string_type& string, U8& value);
- static BOOL convertToS8(const string_type& string, S8& value);
- static BOOL convertToS16(const string_type& string, S16& value);
- static BOOL convertToU16(const string_type& string, U16& value);
- static BOOL convertToU32(const string_type& string, U32& value);
- static BOOL convertToS32(const string_type& string, S32& value);
- static BOOL convertToF32(const string_type& string, F32& value);
- static BOOL convertToF64(const string_type& string, F64& value);
-
- /////////////////////////////////////////////////////////////////////////////////////////
- // Utility functions for working with char*'s and strings
-
- // Like strcmp but also handles empty strings. Uses
- // current locale.
- static S32 compareStrings(const T* lhs, const T* rhs);
- static S32 compareStrings(const string_type& lhs, const string_type& rhs);
-
- // case insensitive version of above. Uses current locale on
- // Win32, and falls back to a non-locale aware comparison on
- // Linux.
- static S32 compareInsensitive(const T* lhs, const T* rhs);
- static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
-
- // Case sensitive comparison with good handling of numbers. Does not use current locale.
- // a.k.a. strdictcmp()
- static S32 compareDict(const string_type& a, const string_type& b);
-
- // Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
- // a.k.a. strdictcmp()
- static S32 compareDictInsensitive(const string_type& a, const string_type& b);
-
- // Puts compareDict() in a form appropriate for LL container classes to use for sorting.
- static BOOL precedesDict( const string_type& a, const string_type& b );
-
- // A replacement for strncpy.
- // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
- // up to dst_size-1 characters of src.
- static void copy(T* dst, const T* src, size_type dst_size);
-
- // Copies src into dst at a given offset.
- static void copyInto(string_type& dst, const string_type& src, size_type offset);
-
- static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
-
-
-#ifdef _DEBUG
- LL_COMMON_API static void testHarness();
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // Static Utility functions that operate on std::strings
+
+ static const string_type null;
+
+ typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
+ /// considers any sequence of delims as a single field separator
+ LL_COMMON_API static void getTokens(const string_type& instr,
+ std::vector<string_type >& tokens,
+ const string_type& delims);
+ /// like simple scan overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& delims);
+ /// add support for keep_delims and quotes (either could be empty string)
+ static void getTokens(const string_type& instr,
+ std::vector<string_type>& tokens,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes=string_type());
+ /// like keep_delims-and-quotes overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes=string_type());
+ /// add support for escapes (could be empty string)
+ static void getTokens(const string_type& instr,
+ std::vector<string_type>& tokens,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes);
+ /// like escapes overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes);
+
+ LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
+ LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
+ LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
+ LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
+ LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
+ LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
+ LL_COMMON_API static void setLocale (std::string inLocale);
+ LL_COMMON_API static std::string getLocale (void);
+
+ static bool isValidIndex(const string_type& string, size_type i)
+ {
+ return !string.empty() && (0 <= i) && (i <= string.size());
+ }
+
+ static bool contains(const string_type& string, T c, size_type i=0)
+ {
+ return string.find(c, i) != string_type::npos;
+ }
+
+ static void trimHead(string_type& string);
+ static void trimTail(string_type& string);
+ static void trim(string_type& string) { trimHead(string); trimTail(string); }
+ static void truncate(string_type& string, size_type count);
+
+ static void toUpper(string_type& string);
+ static void toLower(string_type& string);
+
+ // True if this is the head of s.
+ static BOOL isHead( const string_type& string, const T* s );
+
+ /**
+ * @brief Returns true if string starts with substr
+ *
+ * If etither string or substr are empty, this method returns false.
+ */
+ static bool startsWith(
+ const string_type& string,
+ const string_type& substr);
+
+ /**
+ * @brief Returns true if string ends in substr
+ *
+ * If etither string or substr are empty, this method returns false.
+ */
+ static bool endsWith(
+ const string_type& string,
+ const string_type& substr);
+
+ /**
+ * get environment string value with proper Unicode handling
+ * (key is always UTF-8)
+ * detect absence by return value == dflt
+ */
+ static string_type getenv(const std::string& key, const string_type& dflt="");
+ /**
+ * get optional environment string value with proper Unicode handling
+ * (key is always UTF-8)
+ * detect absence by (! return value)
+ */
+ static boost::optional<string_type> getoptenv(const std::string& key);
+
+ static void addCRLF(string_type& string);
+ static void removeCRLF(string_type& string);
+ static void removeWindowsCR(string_type& string);
+
+ static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
+ static void replaceNonstandardASCII( string_type& string, T replacement );
+ static void replaceChar( string_type& string, T target, T replacement );
+ static void replaceString( string_type& string, string_type target, string_type replacement );
+ static string_type capitalize(const string_type& str);
+ static void capitalize(string_type& str);
+
+ static BOOL containsNonprintable(const string_type& string);
+ static void stripNonprintable(string_type& string);
+
+ /**
+ * Double-quote an argument string if needed, unless it's already
+ * double-quoted. Decide whether it's needed based on the presence of any
+ * character in @a triggers (default space or double-quote). If we quote
+ * it, escape any embedded double-quote with the @a escape string (default
+ * backslash).
+ *
+ * Passing triggers="" means always quote, unless it's already double-quoted.
+ */
+ static string_type quote(const string_type& str,
+ const string_type& triggers=" \"",
+ const string_type& escape="\\");
+
+ /**
+ * @brief Unsafe way to make ascii characters. You should probably
+ * only call this when interacting with the host operating system.
+ * The 1 byte std::string does not work correctly.
+ * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
+ * should work.
+ */
+ static void _makeASCII(string_type& string);
+
+ // Conversion to other data types
+ static BOOL convertToBOOL(const string_type& string, BOOL& value);
+ static BOOL convertToU8(const string_type& string, U8& value);
+ static BOOL convertToS8(const string_type& string, S8& value);
+ static BOOL convertToS16(const string_type& string, S16& value);
+ static BOOL convertToU16(const string_type& string, U16& value);
+ static BOOL convertToU32(const string_type& string, U32& value);
+ static BOOL convertToS32(const string_type& string, S32& value);
+ static BOOL convertToF32(const string_type& string, F32& value);
+ static BOOL convertToF64(const string_type& string, F64& value);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // Utility functions for working with char*'s and strings
+
+ // Like strcmp but also handles empty strings. Uses
+ // current locale.
+ static S32 compareStrings(const T* lhs, const T* rhs);
+ static S32 compareStrings(const string_type& lhs, const string_type& rhs);
+
+ // case insensitive version of above. Uses current locale on
+ // Win32, and falls back to a non-locale aware comparison on
+ // Linux.
+ static S32 compareInsensitive(const T* lhs, const T* rhs);
+ static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
+
+ // Case sensitive comparison with good handling of numbers. Does not use current locale.
+ // a.k.a. strdictcmp()
+ static S32 compareDict(const string_type& a, const string_type& b);
+
+ // Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
+ // a.k.a. strdictcmp()
+ static S32 compareDictInsensitive(const string_type& a, const string_type& b);
+
+ // Puts compareDict() in a form appropriate for LL container classes to use for sorting.
+ static BOOL precedesDict( const string_type& a, const string_type& b );
+
+ // A replacement for strncpy.
+ // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
+ // up to dst_size-1 characters of src.
+ static void copy(T* dst, const T* src, size_type dst_size);
+
+ // Copies src into dst at a given offset.
+ static void copyInto(string_type& dst, const string_type& src, size_type offset);
+
+ static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
+
+
+#ifdef _DEBUG
+ LL_COMMON_API static void testHarness();
#endif
private:
- LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
+ LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
};
template<class T> const std::basic_string<T> LLStringUtilBase<T>::null;
@@ -453,18 +455,18 @@ typedef std::basic_string<llwchar> LLWString;
class LLStringExplicit : public std::string
{
public:
- explicit LLStringExplicit(const char* s) : std::string(s) {}
- LLStringExplicit(const std::string& s) : std::string(s) {}
- LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {}
+ explicit LLStringExplicit(const char* s) : std::string(s) {}
+ LLStringExplicit(const std::string& s) : std::string(s) {}
+ LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {}
};
struct LLDictionaryLess
{
public:
- bool operator()(const std::string& a, const std::string& b) const
- {
- return (LLStringUtil::precedesDict(a, b) ? true : false);
- }
+ bool operator()(const std::string& a, const std::string& b) const
+ {
+ return (LLStringUtil::precedesDict(a, b) ? true : false);
+ }
};
@@ -481,10 +483,10 @@ public:
* @return a copy of in string minus the trailing count bytes.
*/
inline std::string chop_tail_copy(
- const std::string& in,
- std::string::size_type count)
+ const std::string& in,
+ std::string::size_type count)
{
- return std::string(in, 0, in.length() - count);
+ return std::string(in, 0, in.length() - count);
}
/**
@@ -519,11 +521,38 @@ struct ll_convert_impl
TO operator()(const FROM& in) const;
};
-// Use a function template to get the nice ll_convert<TO>(from_value) API.
+/**
+ * somefunction(ll_convert(data))
+ * target = ll_convert(data)
+ * totype otherfunc(const fromtype& data)
+ * {
+ * // ...
+ * return ll_convert(data);
+ * }
+ * all infer both the FROM type and the TO type.
+ */
+template <typename FROM>
+class ll_convert
+{
+private:
+ const FROM& mRef;
+
+public:
+ ll_convert(const FROM& ref): mRef(ref) {}
+
+ template <typename TO>
+ inline operator TO() const
+ {
+ return ll_convert_impl<TO, std::decay_t<const FROM>>()(mRef);
+ }
+};
+
+// When the TO type must be explicit, use a function template to get
+// ll_convert_to<TO>(from_value) API.
template<typename TO, typename FROM>
-TO ll_convert(const FROM& in)
+TO ll_convert_to(const FROM& in)
{
- return ll_convert_impl<TO, FROM>()(in);
+ return ll_convert_impl<TO, std::decay_t<const FROM>>()(in);
}
// degenerate case
@@ -577,8 +606,8 @@ inline size_t ll_convert_length<char> (const char* zstr) { return std::strl
// and longname(const string&, len) so calls written pre-ll_convert() will
// work. Most of these overloads will be unified once we turn on C++17 and can
// use std::string_view.
-// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*)
-// and ll_convert<OUTSTR>(const string&) will work.
+// It also uses aliasmacro to ensure that both ll_convert(const char*)
+// and ll_convert(const string&) will work.
#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \
LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \
inline auto longname(const INSTR& in, size_t len) \
@@ -677,10 +706,10 @@ ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_u
inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);}
// Length of this UTF32 string in bytes when transformed to UTF8
-LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);
+LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);
// Length in bytes of this wide char in a UTF8 string
-LL_COMMON_API S32 wchar_utf8_length(const llwchar wc);
+LL_COMMON_API S32 wchar_utf8_length(const llwchar wc);
LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc);
@@ -697,7 +726,7 @@ LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wst
/**
* @brief Properly truncate a utf8 string to a maximum byte count.
- *
+ *
* The returned string may be less than max_len if the truncation
* happens in the middle of a glyph. If max_len is longer than the
* string passed in, the return value == utf8str.
@@ -710,8 +739,8 @@ LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32
LL_COMMON_API std::string utf8str_trim(const std::string& utf8str);
LL_COMMON_API S32 utf8str_compare_insensitive(
- const std::string& lhs,
- const std::string& rhs);
+ const std::string& lhs,
+ const std::string& rhs);
/**
* @brief Properly truncate a utf8 string to a maximum character count.
@@ -732,14 +761,14 @@ LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, co
* @param replace_char The wchar which is written on replace
*/
LL_COMMON_API std::string utf8str_substChar(
- const std::string& utf8str,
- const llwchar target_char,
- const llwchar replace_char);
+ const std::string& utf8str,
+ const llwchar target_char,
+ const llwchar replace_char);
LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str);
// Hack - used for evil notecards.
-LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str);
+LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str);
LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
@@ -815,7 +844,7 @@ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in
template<typename STRING>
STRING windows_message(unsigned long error)
{
- return ll_convert<STRING>(windows_message<std::wstring>(error));
+ return ll_convert(windows_message<std::wstring>(error));
}
/// There's only one real implementation
@@ -845,52 +874,52 @@ LL_COMMON_API boost::optional<std::string> llstring_getoptenv(const std::string
*/
namespace LLStringFn
{
- /**
- * @brief Replace all non-printable characters with replacement in
- * string.
- * NOTE - this will zap non-ascii
- *
- * @param [in,out] string the to modify. out value is the string
- * with zero non-printable characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_nonprintable_in_ascii(
- std::basic_string<char>& string,
- char replacement);
-
-
- /**
- * @brief Replace all non-printable characters and pipe characters
- * with replacement in a string.
- * NOTE - this will zap non-ascii
- *
- * @param [in,out] the string to modify. out value is the string
- * with zero non-printable characters and zero pipe characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
- char replacement);
-
-
- /**
- * @brief Remove all characters that are not allowed in XML 1.0.
- * Returns a copy of the string with those characters removed.
- * Works with US ASCII and UTF-8 encoded strings. JC
- */
- LL_COMMON_API std::string strip_invalid_xml(const std::string& input);
-
-
- /**
- * @brief Replace all control characters (0 <= c < 0x20) with replacement in
- * string. This is safe for utf-8
- *
- * @param [in,out] string the to modify. out value is the string
- * with zero non-printable characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_ascii_controlchars(
- std::basic_string<char>& string,
- char replacement);
+ /**
+ * @brief Replace all non-printable characters with replacement in
+ * string.
+ * NOTE - this will zap non-ascii
+ *
+ * @param [in,out] string the to modify. out value is the string
+ * with zero non-printable characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_nonprintable_in_ascii(
+ std::basic_string<char>& string,
+ char replacement);
+
+
+ /**
+ * @brief Replace all non-printable characters and pipe characters
+ * with replacement in a string.
+ * NOTE - this will zap non-ascii
+ *
+ * @param [in,out] the string to modify. out value is the string
+ * with zero non-printable characters and zero pipe characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
+ char replacement);
+
+
+ /**
+ * @brief Remove all characters that are not allowed in XML 1.0.
+ * Returns a copy of the string with those characters removed.
+ * Works with US ASCII and UTF-8 encoded strings. JC
+ */
+ LL_COMMON_API std::string strip_invalid_xml(const std::string& input);
+
+
+ /**
+ * @brief Replace all control characters (0 <= c < 0x20) with replacement in
+ * string. This is safe for utf-8
+ *
+ * @param [in,out] string the to modify. out value is the string
+ * with zero non-printable characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_ascii_controlchars(
+ std::basic_string<char>& string,
+ char replacement);
}
////////////////////////////////////////////////////////////
@@ -905,36 +934,36 @@ template <class T>
std::vector<typename LLStringUtilBase<T>::string_type>
LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims)
{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, delims);
- return tokens;
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, delims);
+ return tokens;
}
// static
template <class T>
std::vector<typename LLStringUtilBase<T>::string_type>
LLStringUtilBase<T>::getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes)
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes)
{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, drop_delims, keep_delims, quotes);
- return tokens;
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, drop_delims, keep_delims, quotes);
+ return tokens;
}
// static
template <class T>
std::vector<typename LLStringUtilBase<T>::string_type>
LLStringUtilBase<T>::getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes)
-{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
- return tokens;
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes)
+{
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
+ return tokens;
}
namespace LLStringUtilBaseImpl
@@ -950,62 +979,62 @@ namespace LLStringUtilBaseImpl
template <class T>
struct InString
{
- typedef std::basic_string<T> string_type;
- typedef typename string_type::const_iterator const_iterator;
-
- InString(const_iterator b, const_iterator e):
- mIter(b),
- mEnd(e)
- {}
- virtual ~InString() {}
-
- bool done() const { return mIter == mEnd; }
- /// Is the current character (*mIter) escaped? This implementation can
- /// answer trivially because it doesn't support escapes.
- virtual bool escaped() const { return false; }
- /// Obtain the current character and advance @c mIter.
- virtual T next() { return *mIter++; }
- /// Does the current character match specified character?
- virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
- /// Is the current character any one of the specified characters?
- virtual bool oneof(const string_type& delims) const
- {
- return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
- }
-
- /**
- * Scan forward from @from until either @a delim or end. This is primarily
- * useful for processing quoted substrings.
- *
- * If we do see @a delim, append everything from @from until (excluding)
- * @a delim to @a into, advance @c mIter to skip @a delim, and return @c
- * true.
- *
- * If we do not see @a delim, do not alter @a into or @c mIter and return
- * @c false. Do not pass GO, do not collect $200.
- *
- * @note The @c false case described above implements normal getTokens()
- * treatment of an unmatched open quote: treat the quote character as if
- * escaped, that is, simply collect it as part of the current token. Other
- * plausible behaviors directly affect the way getTokens() deals with an
- * unmatched quote: e.g. throwing an exception to treat it as an error, or
- * assuming a close quote beyond end of string (in which case return @c
- * true).
- */
- virtual bool collect_until(string_type& into, const_iterator from, T delim)
- {
- const_iterator found = std::find(from, mEnd, delim);
- // If we didn't find delim, change nothing, just tell caller.
- if (found == mEnd)
- return false;
- // Found delim! Append everything between from and found.
- into.append(from, found);
- // advance past delim in input
- mIter = found + 1;
- return true;
- }
-
- const_iterator mIter, mEnd;
+ typedef std::basic_string<T> string_type;
+ typedef typename string_type::const_iterator const_iterator;
+
+ InString(const_iterator b, const_iterator e):
+ mIter(b),
+ mEnd(e)
+ {}
+ virtual ~InString() {}
+
+ bool done() const { return mIter == mEnd; }
+ /// Is the current character (*mIter) escaped? This implementation can
+ /// answer trivially because it doesn't support escapes.
+ virtual bool escaped() const { return false; }
+ /// Obtain the current character and advance @c mIter.
+ virtual T next() { return *mIter++; }
+ /// Does the current character match specified character?
+ virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
+ /// Is the current character any one of the specified characters?
+ virtual bool oneof(const string_type& delims) const
+ {
+ return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
+ }
+
+ /**
+ * Scan forward from @from until either @a delim or end. This is primarily
+ * useful for processing quoted substrings.
+ *
+ * If we do see @a delim, append everything from @from until (excluding)
+ * @a delim to @a into, advance @c mIter to skip @a delim, and return @c
+ * true.
+ *
+ * If we do not see @a delim, do not alter @a into or @c mIter and return
+ * @c false. Do not pass GO, do not collect $200.
+ *
+ * @note The @c false case described above implements normal getTokens()
+ * treatment of an unmatched open quote: treat the quote character as if
+ * escaped, that is, simply collect it as part of the current token. Other
+ * plausible behaviors directly affect the way getTokens() deals with an
+ * unmatched quote: e.g. throwing an exception to treat it as an error, or
+ * assuming a close quote beyond end of string (in which case return @c
+ * true).
+ */
+ virtual bool collect_until(string_type& into, const_iterator from, T delim)
+ {
+ const_iterator found = std::find(from, mEnd, delim);
+ // If we didn't find delim, change nothing, just tell caller.
+ if (found == mEnd)
+ return false;
+ // Found delim! Append everything between from and found.
+ into.append(from, found);
+ // advance past delim in input
+ mIter = found + 1;
+ return true;
+ }
+
+ const_iterator mIter, mEnd;
};
/// InString subclass that handles escape characters
@@ -1013,177 +1042,177 @@ template <class T>
class InEscString: public InString<T>
{
public:
- typedef InString<T> super;
- typedef typename super::string_type string_type;
- typedef typename super::const_iterator const_iterator;
- using super::done;
- using super::mIter;
- using super::mEnd;
-
- InEscString(const_iterator b, const_iterator e, const string_type& escapes):
- super(b, e),
- mEscapes(escapes)
- {
- // Even though we've already initialized 'mIter' via our base-class
- // constructor, set it again to check for initial escape char.
- setiter(b);
- }
-
- /// This implementation uses the answer cached by setiter().
- virtual bool escaped() const { return mIsEsc; }
- virtual T next()
- {
- // If we're looking at the escape character of an escape sequence,
- // skip that character. This is the one time we can modify 'mIter'
- // without using setiter: for this one case we DO NOT CARE if the
- // escaped character is itself an escape.
- if (mIsEsc)
- ++mIter;
- // If we were looking at an escape character, this is the escaped
- // character; otherwise it's just the next character.
- T result(*mIter);
- // Advance mIter, checking for escape sequence.
- setiter(mIter + 1);
- return result;
- }
-
- virtual bool is(T ch) const
- {
- // Like base-class is(), except that an escaped character matches
- // nothing.
- return (! done()) && (! mIsEsc) && *mIter == ch;
- }
-
- virtual bool oneof(const string_type& delims) const
- {
- // Like base-class oneof(), except that an escaped character matches
- // nothing.
- return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
- }
-
- virtual bool collect_until(string_type& into, const_iterator from, T delim)
- {
- // Deal with escapes in the characters we collect; that is, an escaped
- // character must become just that character without the preceding
- // escape. Collect characters in a separate string rather than
- // directly appending to 'into' in case we do not find delim, in which
- // case we're supposed to leave 'into' unmodified.
- string_type collected;
- // For scanning purposes, we're going to work directly with 'mIter'.
- // Save its current value in case we fail to see delim.
- const_iterator save_iter(mIter);
- // Okay, set 'mIter', checking for escape.
- setiter(from);
- while (! done())
- {
- // If we see an unescaped delim, stop and report success.
- if ((! mIsEsc) && *mIter == delim)
- {
- // Append collected chars to 'into'.
- into.append(collected);
- // Don't forget to advance 'mIter' past delim.
- setiter(mIter + 1);
- return true;
- }
- // We're not at end, and either we're not looking at delim or it's
- // escaped. Collect this character and keep going.
- collected.push_back(next());
- }
- // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
- // caller.
- setiter(save_iter);
- return false;
- }
+ typedef InString<T> super;
+ typedef typename super::string_type string_type;
+ typedef typename super::const_iterator const_iterator;
+ using super::done;
+ using super::mIter;
+ using super::mEnd;
+
+ InEscString(const_iterator b, const_iterator e, const string_type& escapes):
+ super(b, e),
+ mEscapes(escapes)
+ {
+ // Even though we've already initialized 'mIter' via our base-class
+ // constructor, set it again to check for initial escape char.
+ setiter(b);
+ }
+
+ /// This implementation uses the answer cached by setiter().
+ virtual bool escaped() const { return mIsEsc; }
+ virtual T next()
+ {
+ // If we're looking at the escape character of an escape sequence,
+ // skip that character. This is the one time we can modify 'mIter'
+ // without using setiter: for this one case we DO NOT CARE if the
+ // escaped character is itself an escape.
+ if (mIsEsc)
+ ++mIter;
+ // If we were looking at an escape character, this is the escaped
+ // character; otherwise it's just the next character.
+ T result(*mIter);
+ // Advance mIter, checking for escape sequence.
+ setiter(mIter + 1);
+ return result;
+ }
+
+ virtual bool is(T ch) const
+ {
+ // Like base-class is(), except that an escaped character matches
+ // nothing.
+ return (! done()) && (! mIsEsc) && *mIter == ch;
+ }
+
+ virtual bool oneof(const string_type& delims) const
+ {
+ // Like base-class oneof(), except that an escaped character matches
+ // nothing.
+ return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
+ }
+
+ virtual bool collect_until(string_type& into, const_iterator from, T delim)
+ {
+ // Deal with escapes in the characters we collect; that is, an escaped
+ // character must become just that character without the preceding
+ // escape. Collect characters in a separate string rather than
+ // directly appending to 'into' in case we do not find delim, in which
+ // case we're supposed to leave 'into' unmodified.
+ string_type collected;
+ // For scanning purposes, we're going to work directly with 'mIter'.
+ // Save its current value in case we fail to see delim.
+ const_iterator save_iter(mIter);
+ // Okay, set 'mIter', checking for escape.
+ setiter(from);
+ while (! done())
+ {
+ // If we see an unescaped delim, stop and report success.
+ if ((! mIsEsc) && *mIter == delim)
+ {
+ // Append collected chars to 'into'.
+ into.append(collected);
+ // Don't forget to advance 'mIter' past delim.
+ setiter(mIter + 1);
+ return true;
+ }
+ // We're not at end, and either we're not looking at delim or it's
+ // escaped. Collect this character and keep going.
+ collected.push_back(next());
+ }
+ // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
+ // caller.
+ setiter(save_iter);
+ return false;
+ }
private:
- void setiter(const_iterator i)
- {
- mIter = i;
-
- // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
- // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
- // contains(mEscapes, *mIter).
-
- // We're looking at an escaped char if we're not already at end (that
- // is, *mIter is even meaningful); if *mIter is in fact one of the
- // specified escape characters; and if there's one more character
- // following it. That is, if an escape character is the very last
- // character of the input string, it loses its special meaning.
- mIsEsc = (! done()) &&
- LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
- (mIter+1) != mEnd;
- }
-
- const string_type mEscapes;
- bool mIsEsc;
+ void setiter(const_iterator i)
+ {
+ mIter = i;
+
+ // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
+ // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
+ // contains(mEscapes, *mIter).
+
+ // We're looking at an escaped char if we're not already at end (that
+ // is, *mIter is even meaningful); if *mIter is in fact one of the
+ // specified escape characters; and if there's one more character
+ // following it. That is, if an escape character is the very last
+ // character of the input string, it loses its special meaning.
+ mIsEsc = (! done()) &&
+ LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
+ (mIter+1) != mEnd;
+ }
+
+ const string_type mEscapes;
+ bool mIsEsc;
};
/// getTokens() implementation based on InString concept
template <typename INSTRING, typename string_type>
void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes)
-{
- // There are times when we want to match either drop_delims or
- // keep_delims. Concatenate them up front to speed things up.
- string_type all_delims(drop_delims + keep_delims);
- // no tokens yet
- tokens.clear();
-
- // try for another token
- while (! instr.done())
- {
- // scan past any drop_delims
- while (instr.oneof(drop_delims))
- {
- // skip this drop_delim
- instr.next();
- // but if that was the end of the string, done
- if (instr.done())
- return;
- }
- // found the start of another token: make a slot for it
- tokens.push_back(string_type());
- if (instr.oneof(keep_delims))
- {
- // *iter is a keep_delim, a token of exactly 1 character. Append
- // that character to the new token and proceed.
- tokens.back().push_back(instr.next());
- continue;
- }
- // Here we have a non-delimiter token, which might consist of a mix of
- // quoted and unquoted parts. Use bash rules for quoting: you can
- // embed a quoted substring in the midst of an unquoted token (e.g.
- // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
- // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
- // from bash in that bash considers an unmatched quote an error. Our
- // param signature doesn't allow for errors, so just pretend it's not
- // a quote and embed it.
- // At this level, keep scanning until we hit the next delimiter of
- // either type (drop_delims or keep_delims).
- while (! instr.oneof(all_delims))
- {
- // If we're looking at an open quote, search forward for
- // a close quote, collecting characters along the way.
- if (instr.oneof(quotes) &&
- instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
- {
- // collect_until is cleverly designed to do exactly what we
- // need here. No further action needed if it returns true.
- }
- else
- {
- // Either *iter isn't a quote, or there's no matching close
- // quote: in other words, just an ordinary char. Append it to
- // current token.
- tokens.back().push_back(instr.next());
- }
- // having scanned that segment of this token, if we've reached the
- // end of the string, we're done
- if (instr.done())
- return;
- }
- }
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes)
+{
+ // There are times when we want to match either drop_delims or
+ // keep_delims. Concatenate them up front to speed things up.
+ string_type all_delims(drop_delims + keep_delims);
+ // no tokens yet
+ tokens.clear();
+
+ // try for another token
+ while (! instr.done())
+ {
+ // scan past any drop_delims
+ while (instr.oneof(drop_delims))
+ {
+ // skip this drop_delim
+ instr.next();
+ // but if that was the end of the string, done
+ if (instr.done())
+ return;
+ }
+ // found the start of another token: make a slot for it
+ tokens.push_back(string_type());
+ if (instr.oneof(keep_delims))
+ {
+ // *iter is a keep_delim, a token of exactly 1 character. Append
+ // that character to the new token and proceed.
+ tokens.back().push_back(instr.next());
+ continue;
+ }
+ // Here we have a non-delimiter token, which might consist of a mix of
+ // quoted and unquoted parts. Use bash rules for quoting: you can
+ // embed a quoted substring in the midst of an unquoted token (e.g.
+ // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
+ // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
+ // from bash in that bash considers an unmatched quote an error. Our
+ // param signature doesn't allow for errors, so just pretend it's not
+ // a quote and embed it.
+ // At this level, keep scanning until we hit the next delimiter of
+ // either type (drop_delims or keep_delims).
+ while (! instr.oneof(all_delims))
+ {
+ // If we're looking at an open quote, search forward for
+ // a close quote, collecting characters along the way.
+ if (instr.oneof(quotes) &&
+ instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
+ {
+ // collect_until is cleverly designed to do exactly what we
+ // need here. No further action needed if it returns true.
+ }
+ else
+ {
+ // Either *iter isn't a quote, or there's no matching close
+ // quote: in other words, just an ordinary char. Append it to
+ // current token.
+ tokens.back().push_back(instr.next());
+ }
+ // having scanned that segment of this token, if we've reached the
+ // end of the string, we're done
+ if (instr.done())
+ return;
+ }
+ }
}
} // namespace LLStringUtilBaseImpl
@@ -1191,256 +1220,256 @@ void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
// static
template <class T>
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes)
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes)
{
- // Because this overload doesn't support escapes, use simple InString to
- // manage input range.
- LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
- LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
+ // Because this overload doesn't support escapes, use simple InString to
+ // manage input range.
+ LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
+ LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
}
// static
template <class T>
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes, const string_type& escapes)
-{
- // This overload must deal with escapes. Delegate that to InEscString
- // (unless there ARE no escapes).
- std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
- if (escapes.empty())
- instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
- else
- instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
- LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes, const string_type& escapes)
+{
+ // This overload must deal with escapes. Delegate that to InEscString
+ // (unless there ARE no escapes).
+ std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
+ if (escapes.empty())
+ instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
+ else
+ instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
+ LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
}
// static
-template<class T>
+template<class T>
S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)
-{
- S32 result;
- if( lhs == rhs )
- {
- result = 0;
- }
- else
- if ( !lhs || !lhs[0] )
- {
- result = ((!rhs || !rhs[0]) ? 0 : 1);
- }
- else
- if ( !rhs || !rhs[0])
- {
- result = -1;
- }
- else
- {
- result = LLStringOps::collate(lhs, rhs);
- }
- return result;
+{
+ S32 result;
+ if( lhs == rhs )
+ {
+ result = 0;
+ }
+ else
+ if ( !lhs || !lhs[0] )
+ {
+ result = ((!rhs || !rhs[0]) ? 0 : 1);
+ }
+ else
+ if ( !rhs || !rhs[0])
+ {
+ result = -1;
+ }
+ else
+ {
+ result = LLStringOps::collate(lhs, rhs);
+ }
+ return result;
}
-//static
-template<class T>
+//static
+template<class T>
S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)
{
- return LLStringOps::collate(lhs.c_str(), rhs.c_str());
+ return LLStringOps::collate(lhs.c_str(), rhs.c_str());
}
// static
-template<class T>
+template<class T>
S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
{
- S32 result;
- if( lhs == rhs )
- {
- result = 0;
- }
- else
- if ( !lhs || !lhs[0] )
- {
- result = ((!rhs || !rhs[0]) ? 0 : 1);
- }
- else
- if ( !rhs || !rhs[0] )
- {
- result = -1;
- }
- else
- {
- string_type lhs_string(lhs);
- string_type rhs_string(rhs);
- LLStringUtilBase<T>::toUpper(lhs_string);
- LLStringUtilBase<T>::toUpper(rhs_string);
- result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
- }
- return result;
+ S32 result;
+ if( lhs == rhs )
+ {
+ result = 0;
+ }
+ else
+ if ( !lhs || !lhs[0] )
+ {
+ result = ((!rhs || !rhs[0]) ? 0 : 1);
+ }
+ else
+ if ( !rhs || !rhs[0] )
+ {
+ result = -1;
+ }
+ else
+ {
+ string_type lhs_string(lhs);
+ string_type rhs_string(rhs);
+ LLStringUtilBase<T>::toUpper(lhs_string);
+ LLStringUtilBase<T>::toUpper(rhs_string);
+ result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
+ }
+ return result;
}
-//static
-template<class T>
+//static
+template<class T>
S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)
{
- string_type lhs_string(lhs);
- string_type rhs_string(rhs);
- LLStringUtilBase<T>::toUpper(lhs_string);
- LLStringUtilBase<T>::toUpper(rhs_string);
- return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
+ string_type lhs_string(lhs);
+ string_type rhs_string(rhs);
+ LLStringUtilBase<T>::toUpper(lhs_string);
+ LLStringUtilBase<T>::toUpper(rhs_string);
+ return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
}
// Case sensitive comparison with good handling of numbers. Does not use current locale.
// a.k.a. strdictcmp()
-//static
+//static
template<class T>
S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)
{
- const T* a = astr.c_str();
- const T* b = bstr.c_str();
- T ca, cb;
- S32 ai, bi, cnt = 0;
- S32 bias = 0;
-
- ca = *(a++);
- cb = *(b++);
- while( ca && cb ){
- if( bias==0 ){
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; }
- }else{
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
- }
- if( LLStringOps::isDigit(ca) ){
- if( cnt-->0 ){
- if( cb!=ca ) break;
- }else{
- if( !LLStringOps::isDigit(cb) ) break;
- for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
- for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
- if( ai<bi ){ ca=0; break; }
- if( bi<ai ){ cb=0; break; }
- if( ca!=cb ) break;
- cnt = ai;
- }
- }else if( ca!=cb ){ break;
- }
- ca = *(a++);
- cb = *(b++);
- }
- if( ca==cb ) ca += bias;
- return ca-cb;
+ const T* a = astr.c_str();
+ const T* b = bstr.c_str();
+ T ca, cb;
+ S32 ai, bi, cnt = 0;
+ S32 bias = 0;
+
+ ca = *(a++);
+ cb = *(b++);
+ while( ca && cb ){
+ if( bias==0 ){
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; }
+ }else{
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
+ }
+ if( LLStringOps::isDigit(ca) ){
+ if( cnt-->0 ){
+ if( cb!=ca ) break;
+ }else{
+ if( !LLStringOps::isDigit(cb) ) break;
+ for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
+ for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
+ if( ai<bi ){ ca=0; break; }
+ if( bi<ai ){ cb=0; break; }
+ if( ca!=cb ) break;
+ cnt = ai;
+ }
+ }else if( ca!=cb ){ break;
+ }
+ ca = *(a++);
+ cb = *(b++);
+ }
+ if( ca==cb ) ca += bias;
+ return ca-cb;
}
// static
template<class T>
S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)
{
- const T* a = astr.c_str();
- const T* b = bstr.c_str();
- T ca, cb;
- S32 ai, bi, cnt = 0;
-
- ca = *(a++);
- cb = *(b++);
- while( ca && cb ){
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
- if( LLStringOps::isDigit(ca) ){
- if( cnt-->0 ){
- if( cb!=ca ) break;
- }else{
- if( !LLStringOps::isDigit(cb) ) break;
- for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
- for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
- if( ai<bi ){ ca=0; break; }
- if( bi<ai ){ cb=0; break; }
- if( ca!=cb ) break;
- cnt = ai;
- }
- }else if( ca!=cb ){ break;
- }
- ca = *(a++);
- cb = *(b++);
- }
- return ca-cb;
+ const T* a = astr.c_str();
+ const T* b = bstr.c_str();
+ T ca, cb;
+ S32 ai, bi, cnt = 0;
+
+ ca = *(a++);
+ cb = *(b++);
+ while( ca && cb ){
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
+ if( LLStringOps::isDigit(ca) ){
+ if( cnt-->0 ){
+ if( cb!=ca ) break;
+ }else{
+ if( !LLStringOps::isDigit(cb) ) break;
+ for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
+ for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
+ if( ai<bi ){ ca=0; break; }
+ if( bi<ai ){ cb=0; break; }
+ if( ca!=cb ) break;
+ cnt = ai;
+ }
+ }else if( ca!=cb ){ break;
+ }
+ ca = *(a++);
+ cb = *(b++);
+ }
+ return ca-cb;
}
// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
-// static
-template<class T>
+// static
+template<class T>
BOOL LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )
{
- if( a.size() && b.size() )
- {
- return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0);
- }
- else
- {
- return (!b.empty());
- }
+ if( a.size() && b.size() )
+ {
+ return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0);
+ }
+ else
+ {
+ return (!b.empty());
+ }
}
//static
-template<class T>
-void LLStringUtilBase<T>::toUpper(string_type& string)
-{
- if( !string.empty() )
- {
- std::transform(
- string.begin(),
- string.end(),
- string.begin(),
- (T(*)(T)) &LLStringOps::toUpper);
- }
+template<class T>
+void LLStringUtilBase<T>::toUpper(string_type& string)
+{
+ if( !string.empty() )
+ {
+ std::transform(
+ string.begin(),
+ string.end(),
+ string.begin(),
+ (T(*)(T)) &LLStringOps::toUpper);
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::toLower(string_type& string)
-{
- if( !string.empty() )
- {
- std::transform(
- string.begin(),
- string.end(),
- string.begin(),
- (T(*)(T)) &LLStringOps::toLower);
- }
+{
+ if( !string.empty() )
+ {
+ std::transform(
+ string.begin(),
+ string.end(),
+ string.begin(),
+ (T(*)(T)) &LLStringOps::toLower);
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::trimHead(string_type& string)
-{
- if( !string.empty() )
- {
- size_type i = 0;
- while( i < string.length() && LLStringOps::isSpace( string[i] ) )
- {
- i++;
- }
- string.erase(0, i);
- }
+{
+ if( !string.empty() )
+ {
+ size_type i = 0;
+ while( i < string.length() && LLStringOps::isSpace( string[i] ) )
+ {
+ i++;
+ }
+ string.erase(0, i);
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::trimTail(string_type& string)
-{
- if( string.size() )
- {
- size_type len = string.length();
- size_type i = len;
- while( i > 0 && LLStringOps::isSpace( string[i-1] ) )
- {
- i--;
- }
-
- string.erase( i, len - i );
- }
+{
+ if( string.size() )
+ {
+ size_type len = string.length();
+ size_type i = len;
+ while( i > 0 && LLStringOps::isSpace( string[i-1] ) )
+ {
+ i--;
+ }
+
+ string.erase( i, len - i );
+ }
}
@@ -1449,67 +1478,67 @@ void LLStringUtilBase<T>::trimTail(string_type& string)
template<class T>
void LLStringUtilBase<T>::addCRLF(string_type& string)
{
- const T LF = 10;
- const T CR = 13;
-
- // Count the number of line feeds
- size_type count = 0;
- size_type len = string.size();
- size_type i;
- for( i = 0; i < len; i++ )
- {
- if( string[i] == LF )
- {
- count++;
- }
- }
-
- // Insert a carriage return before each line feed
- if( count )
- {
- size_type size = len + count;
- T *t = new T[size];
- size_type j = 0;
- for( i = 0; i < len; ++i )
- {
- if( string[i] == LF )
- {
- t[j] = CR;
- ++j;
- }
- t[j] = string[i];
- ++j;
- }
-
- string.assign(t, size);
- delete[] t;
- }
+ const T LF = 10;
+ const T CR = 13;
+
+ // Count the number of line feeds
+ size_type count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len; i++ )
+ {
+ if( string[i] == LF )
+ {
+ count++;
+ }
+ }
+
+ // Insert a carriage return before each line feed
+ if( count )
+ {
+ size_type size = len + count;
+ T *t = new T[size];
+ size_type j = 0;
+ for( i = 0; i < len; ++i )
+ {
+ if( string[i] == LF )
+ {
+ t[j] = CR;
+ ++j;
+ }
+ t[j] = string[i];
+ ++j;
+ }
+
+ string.assign(t, size);
+ delete[] t;
+ }
}
// Remove all carriage returns
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::removeCRLF(string_type& string)
{
- const T CR = 13;
-
- size_type cr_count = 0;
- size_type len = string.size();
- size_type i;
- for( i = 0; i < len - cr_count; i++ )
- {
- if( string[i+cr_count] == CR )
- {
- cr_count++;
- }
-
- string[i] = string[i+cr_count];
- }
- string.erase(i, cr_count);
+ const T CR = 13;
+
+ size_type cr_count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len - cr_count; i++ )
+ {
+ if( string[i+cr_count] == CR )
+ {
+ cr_count++;
+ }
+
+ string[i] = string[i+cr_count];
+ }
+ string.erase(i, cr_count);
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
{
if (string.empty())
@@ -1538,269 +1567,269 @@ void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
template<class T>
void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
{
- size_type found_pos = 0;
- while( (found_pos = string.find(target, found_pos)) != string_type::npos )
- {
- string[found_pos] = replacement;
- found_pos++; // avoid infinite defeat if target == replacement
- }
+ size_type found_pos = 0;
+ while( (found_pos = string.find(target, found_pos)) != string_type::npos )
+ {
+ string[found_pos] = replacement;
+ found_pos++; // avoid infinite defeat if target == replacement
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement )
{
- size_type found_pos = 0;
- while( (found_pos = string.find(target, found_pos)) != string_type::npos )
- {
- string.replace( found_pos, target.length(), replacement );
- found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
- }
+ size_type found_pos = 0;
+ while( (found_pos = string.find(target, found_pos)) != string_type::npos )
+ {
+ string.replace( found_pos, target.length(), replacement );
+ found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )
{
- const char LF = 10;
- const S8 MIN = 32;
-// const S8 MAX = 127;
-
- size_type len = string.size();
- for( size_type i = 0; i < len; i++ )
- {
- // No need to test MAX < mText[i] because we treat mText[i] as a signed char,
- // which has a max value of 127.
- if( ( S8(string[i]) < MIN ) && (string[i] != LF) )
- {
- string[i] = replacement;
- }
- }
+ const char LF = 10;
+ const S8 MIN = 32;
+// const S8 MAX = 127;
+
+ size_type len = string.size();
+ for( size_type i = 0; i < len; i++ )
+ {
+ // No need to test MAX < mText[i] because we treat mText[i] as a signed char,
+ // which has a max value of 127.
+ if( ( S8(string[i]) < MIN ) && (string[i] != LF) )
+ {
+ string[i] = replacement;
+ }
+ }
}
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )
{
- const T TAB = '\t';
- const T SPACE = ' ';
-
- string_type out_str;
- // Replace tabs with spaces
- for (size_type i = 0; i < str.length(); i++)
- {
- if (str[i] == TAB)
- {
- for (size_type j = 0; j < spaces_per_tab; j++)
- out_str += SPACE;
- }
- else
- {
- out_str += str[i];
- }
- }
- str = out_str;
+ const T TAB = '\t';
+ const T SPACE = ' ';
+
+ string_type out_str;
+ // Replace tabs with spaces
+ for (size_type i = 0; i < str.length(); i++)
+ {
+ if (str[i] == TAB)
+ {
+ for (size_type j = 0; j < spaces_per_tab; j++)
+ out_str += SPACE;
+ }
+ else
+ {
+ out_str += str[i];
+ }
+ }
+ str = out_str;
}
//static
template<class T>
std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str)
{
- string_type result(str);
- capitalize(result);
- return result;
+ string_type result(str);
+ capitalize(result);
+ return result;
}
//static
template<class T>
void LLStringUtilBase<T>::capitalize(string_type& str)
{
- if (str.size())
- {
- auto last = str[0] = toupper(str[0]);
- for (U32 i = 1; i < str.size(); ++i)
- {
- last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i];
- }
- }
+ if (str.size())
+ {
+ auto last = str[0] = toupper(str[0]);
+ for (U32 i = 1; i < str.size(); ++i)
+ {
+ last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i];
+ }
+ }
}
//static
-template<class T>
+template<class T>
BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string)
{
- const char MIN = 32;
- BOOL rv = FALSE;
- for (size_type i = 0; i < string.size(); i++)
- {
- if(string[i] < MIN)
- {
- rv = TRUE;
- break;
- }
- }
- return rv;
+ const char MIN = 32;
+ BOOL rv = FALSE;
+ for (size_type i = 0; i < string.size(); i++)
+ {
+ if(string[i] < MIN)
+ {
+ rv = TRUE;
+ break;
+ }
+ }
+ return rv;
}
-// *TODO: reimplement in terms of algorithm
+// *TODO: reimplement in terms of algorithm
//static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::stripNonprintable(string_type& string)
{
- const char MIN = 32;
- size_type j = 0;
- if (string.empty())
- {
- return;
- }
- size_t src_size = string.size();
- char* c_string = new char[src_size + 1];
- if(c_string == NULL)
- {
- return;
- }
- copy(c_string, string.c_str(), src_size+1);
- char* write_head = &c_string[0];
- for (size_type i = 0; i < src_size; i++)
- {
- char* read_head = &string[i];
- write_head = &c_string[j];
- if(!(*read_head < MIN))
- {
- *write_head = *read_head;
- ++j;
- }
- }
- c_string[j]= '\0';
- string = c_string;
- delete []c_string;
+ const char MIN = 32;
+ size_type j = 0;
+ if (string.empty())
+ {
+ return;
+ }
+ size_t src_size = string.size();
+ char* c_string = new char[src_size + 1];
+ if(c_string == NULL)
+ {
+ return;
+ }
+ copy(c_string, string.c_str(), src_size+1);
+ char* write_head = &c_string[0];
+ for (size_type i = 0; i < src_size; i++)
+ {
+ char* read_head = &string[i];
+ write_head = &c_string[j];
+ if(!(*read_head < MIN))
+ {
+ *write_head = *read_head;
+ ++j;
+ }
+ }
+ c_string[j]= '\0';
+ string = c_string;
+ delete []c_string;
}
-// *TODO: reimplement in terms of algorithm
+// *TODO: reimplement in terms of algorithm
template<class T>
std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str,
- const string_type& triggers,
- const string_type& escape)
-{
- size_type len(str.length());
- // If the string is already quoted, assume user knows what s/he's doing.
- if (len >= 2 && str[0] == '"' && str[len-1] == '"')
- {
- return str;
- }
-
- // Not already quoted: do we need to? triggers.empty() is a special case
- // meaning "always quote."
- if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
- {
- // no trigger characters, don't bother quoting
- return str;
- }
-
- // For whatever reason, we must quote this string.
- string_type result;
- result.push_back('"');
- for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
- {
- if (*ci == '"')
- {
- result.append(escape);
- }
- result.push_back(*ci);
- }
- result.push_back('"');
- return result;
+ const string_type& triggers,
+ const string_type& escape)
+{
+ size_type len(str.length());
+ // If the string is already quoted, assume user knows what s/he's doing.
+ if (len >= 2 && str[0] == '"' && str[len-1] == '"')
+ {
+ return str;
+ }
+
+ // Not already quoted: do we need to? triggers.empty() is a special case
+ // meaning "always quote."
+ if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
+ {
+ // no trigger characters, don't bother quoting
+ return str;
+ }
+
+ // For whatever reason, we must quote this string.
+ string_type result;
+ result.push_back('"');
+ for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
+ {
+ if (*ci == '"')
+ {
+ result.append(escape);
+ }
+ result.push_back(*ci);
+ }
+ result.push_back('"');
+ return result;
}
-template<class T>
+template<class T>
void LLStringUtilBase<T>::_makeASCII(string_type& string)
{
- // Replace non-ASCII chars with LL_UNKNOWN_CHAR
- for (size_type i = 0; i < string.length(); i++)
- {
- if (string[i] > 0x7f)
- {
- string[i] = LL_UNKNOWN_CHAR;
- }
- }
+ // Replace non-ASCII chars with LL_UNKNOWN_CHAR
+ for (size_type i = 0; i < string.length(); i++)
+ {
+ if (string[i] > 0x7f)
+ {
+ string[i] = LL_UNKNOWN_CHAR;
+ }
+ }
}
// static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )
{
- if( dst_size > 0 )
- {
- size_type min_len = 0;
- if( src )
- {
- min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */
- memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */
- }
- dst[min_len] = '\0';
- }
+ if( dst_size > 0 )
+ {
+ size_type min_len = 0;
+ if( src )
+ {
+ min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */
+ memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */
+ }
+ dst[min_len] = '\0';
+ }
}
// static
-template<class T>
+template<class T>
void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)
{
- if ( offset == dst.length() )
- {
- // special case - append to end of string and avoid expensive
- // (when strings are large) string manipulations
- dst += src;
- }
- else
- {
- string_type tail = dst.substr(offset);
-
- dst = dst.substr(0, offset);
- dst += src;
- dst += tail;
- };
+ if ( offset == dst.length() )
+ {
+ // special case - append to end of string and avoid expensive
+ // (when strings are large) string manipulations
+ dst += src;
+ }
+ else
+ {
+ string_type tail = dst.substr(offset);
+
+ dst = dst.substr(0, offset);
+ dst += src;
+ dst += tail;
+ };
}
// True if this is the head of s.
//static
-template<class T>
-BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
-{
- if( string.empty() )
- {
- // Early exit
- return FALSE;
- }
- else
- {
- return (strncmp( s, string.c_str(), string.size() ) == 0);
- }
+template<class T>
+BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
+{
+ if( string.empty() )
+ {
+ // Early exit
+ return FALSE;
+ }
+ else
+ {
+ return (strncmp( s, string.c_str(), string.size() ) == 0);
+ }
}
// static
-template<class T>
+template<class T>
bool LLStringUtilBase<T>::startsWith(
- const string_type& string,
- const string_type& substr)
+ const string_type& string,
+ const string_type& substr)
{
- if(string.empty() || (substr.empty())) return false;
- if (substr.length() > string.length()) return false;
- if (0 == string.compare(0, substr.length(), substr)) return true;
- return false;
+ if(string.empty() || (substr.empty())) return false;
+ if (substr.length() > string.length()) return false;
+ if (0 == string.compare(0, substr.length(), substr)) return true;
+ return false;
}
// static
-template<class T>
+template<class T>
bool LLStringUtilBase<T>::endsWith(
- const string_type& string,
- const string_type& substr)
-{
- if(string.empty() || (substr.empty())) return false;
- size_t sub_len = substr.length();
- size_t str_len = string.length();
- if (sub_len > str_len) return false;
- if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true;
- return false;
+ const string_type& string,
+ const string_type& substr)
+{
+ if(string.empty() || (substr.empty())) return false;
+ size_t sub_len = substr.length();
+ size_t str_len = string.length();
+ if (sub_len > str_len) return false;
+ if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true;
+ return false;
}
// static
@@ -1811,7 +1840,7 @@ auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<s
if (found)
{
// return populated boost::optional
- return { ll_convert<string_type>(*found) };
+ return { ll_convert_to<string_type>(*found) };
}
else
{
@@ -1835,187 +1864,187 @@ auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt
}
}
-template<class T>
+template<class T>
BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value)
{
- if( string.empty() )
- {
- return FALSE;
- }
-
- string_type temp( string );
- trim(temp);
- if(
- (temp == "1") ||
- (temp == "T") ||
- (temp == "t") ||
- (temp == "TRUE") ||
- (temp == "true") ||
- (temp == "True") )
- {
- value = TRUE;
- return TRUE;
- }
- else
- if(
- (temp == "0") ||
- (temp == "F") ||
- (temp == "f") ||
- (temp == "FALSE") ||
- (temp == "false") ||
- (temp == "False") )
- {
- value = FALSE;
- return TRUE;
- }
-
- return FALSE;
+ if( string.empty() )
+ {
+ return FALSE;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ if(
+ (temp == "1") ||
+ (temp == "T") ||
+ (temp == "t") ||
+ (temp == "TRUE") ||
+ (temp == "true") ||
+ (temp == "True") )
+ {
+ value = TRUE;
+ return TRUE;
+ }
+ else
+ if(
+ (temp == "0") ||
+ (temp == "F") ||
+ (temp == "f") ||
+ (temp == "FALSE") ||
+ (temp == "false") ||
+ (temp == "False") )
+ {
+ value = FALSE;
+ return TRUE;
+ }
+
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
-{
- S32 value32 = 0;
- BOOL success = convertToS32(string, value32);
- if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) )
- {
- value = (U8) value32;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
+{
+ S32 value32 = 0;
+ BOOL success = convertToS32(string, value32);
+ if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) )
+ {
+ value = (U8) value32;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
-{
- S32 value32 = 0;
- BOOL success = convertToS32(string, value32);
- if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) )
- {
- value = (S8) value32;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
+{
+ S32 value32 = 0;
+ BOOL success = convertToS32(string, value32);
+ if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) )
+ {
+ value = (S8) value32;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
-{
- S32 value32 = 0;
- BOOL success = convertToS32(string, value32);
- if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) )
- {
- value = (S16) value32;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
+{
+ S32 value32 = 0;
+ BOOL success = convertToS32(string, value32);
+ if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) )
+ {
+ value = (S16) value32;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
-{
- S32 value32 = 0;
- BOOL success = convertToS32(string, value32);
- if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) )
- {
- value = (U16) value32;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
+{
+ S32 value32 = 0;
+ BOOL success = convertToS32(string, value32);
+ if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) )
+ {
+ value = (U16) value32;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
-{
- if( string.empty() )
- {
- return FALSE;
- }
-
- string_type temp( string );
- trim(temp);
- U32 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- value = v;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
+{
+ if( string.empty() )
+ {
+ return FALSE;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ U32 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ value = v;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
-{
- if( string.empty() )
- {
- return FALSE;
- }
-
- string_type temp( string );
- trim(temp);
- S32 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- //TODO: figure out overflow and underflow reporting here
- //if((LONG_MAX == v) || (LONG_MIN == v))
- //{
- // // Underflow or overflow
- // return FALSE;
- //}
-
- value = v;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
+{
+ if( string.empty() )
+ {
+ return FALSE;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ S32 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ //TODO: figure out overflow and underflow reporting here
+ //if((LONG_MAX == v) || (LONG_MIN == v))
+ //{
+ // // Underflow or overflow
+ // return FALSE;
+ //}
+
+ value = v;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
-BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
-{
- F64 value64 = 0.0;
- BOOL success = convertToF64(string, value64);
- if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) )
- {
- value = (F32) value64;
- return TRUE;
- }
- return FALSE;
+template<class T>
+BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
+{
+ F64 value64 = 0.0;
+ BOOL success = convertToF64(string, value64);
+ if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) )
+ {
+ value = (F32) value64;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
+template<class T>
BOOL LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)
{
- if( string.empty() )
- {
- return FALSE;
- }
-
- string_type temp( string );
- trim(temp);
- F64 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- //TODO: figure out overflow and underflow reporting here
- //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) )
- //{
- // // Underflow or overflow
- // return FALSE;
- //}
-
- value = v;
- return TRUE;
- }
- return FALSE;
+ if( string.empty() )
+ {
+ return FALSE;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ F64 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ //TODO: figure out overflow and underflow reporting here
+ //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) )
+ //{
+ // // Underflow or overflow
+ // return FALSE;
+ //}
+
+ value = v;
+ return TRUE;
+ }
+ return FALSE;
}
-template<class T>
+template<class T>
void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
{
- size_type cur_size = string.size();
- string.resize(count < cur_size ? count : cur_size);
+ size_type cur_size = string.size();
+ string.resize(count < cur_size ? count : cur_size);
}
// The good thing about *declaration* macros, vs. usage macros, is that now
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
new file mode 100644
index 0000000000..08bc65e0c5
--- /dev/null
+++ b/indra/llcommon/lua_function.cpp
@@ -0,0 +1,984 @@
+/**
+ * @file lua_function.cpp
+ * @author Nat Goodspeed
+ * @date 2024-02-05
+ * @brief Implementation for lua_function.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lua_function.h"
+// STL headers
+// std headers
+#include <algorithm>
+#include <exception>
+#include <iomanip> // std::quoted
+#include <map>
+#include <memory> // std::unique_ptr
+#include <typeinfo>
+// external library headers
+// other Linden headers
+#include "fsyspath.h"
+#include "hexdump.h"
+#include "lleventcoro.h"
+#include "llsd.h"
+#include "llsdutil.h"
+#include "lualistener.h"
+#include "stringize.h"
+
+const S32 INTERRUPTS_MAX_LIMIT = 20000;
+const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
+
+#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
+#define lua_rawlen lua_objlen
+
+/*****************************************************************************
+* luau namespace
+*****************************************************************************/
+namespace
+{
+ // can't specify free function free() as a unique_ptr deleter
+ struct freer
+ {
+ void operator()(void* ptr){ free(ptr); }
+ };
+} // anonymous namespace
+
+int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text)
+{
+ auto r = loadstring(L, desc, text);
+ if (r != LUA_OK)
+ return r;
+
+ // It's important to pass LUA_MULTRET as the expected number of return
+ // values: if we pass any fixed number, we discard any returned values
+ // beyond that number.
+ return lua_pcall(L, 0, LUA_MULTRET, 0);
+}
+
+int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &text)
+{
+ size_t bytecodeSize = 0;
+ // The char* returned by luau_compile() must be freed by calling free().
+ // Use unique_ptr so the memory will be freed even if luau_load() throws.
+ std::unique_ptr<char[], freer> bytecode{
+ luau_compile(text.data(), text.length(), nullptr, &bytecodeSize)};
+ return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0);
+}
+
+fsyspath lluau::source_path(lua_State* L)
+{
+ //Luau lua_Debug and lua_getinfo() are different compared to default Lua:
+ //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h
+ lua_Debug ar;
+ lua_getinfo(L, 1, "s", &ar);
+ return ar.source;
+}
+
+void lluau::set_interrupts_counter(lua_State *L, S32 counter)
+{
+ luaL_checkstack(L, 2, nullptr);
+ lua_pushstring(L, "_INTERRUPTS");
+ lua_pushinteger(L, counter);
+ lua_rawset(L, LUA_REGISTRYINDEX);
+}
+
+void lluau::check_interrupts_counter(lua_State* L)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushstring(L, "_INTERRUPTS");
+ lua_rawget(L, LUA_REGISTRYINDEX);
+ S32 counter = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+
+ lluau::set_interrupts_counter(L, ++counter);
+ if (counter > INTERRUPTS_MAX_LIMIT)
+ {
+ lluau::error(L, "Possible infinite loop, terminated.");
+ }
+ else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0)
+ {
+ LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << counter << " interrupts"
+ << LL_ENDL;
+ llcoro::suspend();
+ }
+}
+
+/*****************************************************************************
+* Lua <=> C++ conversions
+*****************************************************************************/
+std::string lua_tostdstring(lua_State* L, int index)
+{
+ size_t len;
+ const char* strval{ lua_tolstring(L, index, &len) };
+ return { strval, len };
+}
+
+void lua_pushstdstring(lua_State* L, const std::string& str)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushlstring(L, str.c_str(), str.length());
+}
+
+// By analogy with existing lua_tomumble() functions, return an LLSD object
+// corresponding to the Lua object at stack index 'index' in state L.
+// This function assumes that a Lua caller is fully aware that they're trying
+// to call a viewer function. In other words, the caller must specifically
+// construct Lua data convertible to LLSD.
+//
+// For proper error handling, we REQUIRE that the Lua runtime be compiled as
+// C++ so errors are raised as C++ exceptions rather than as longjmp() calls:
+// http://www.lua.org/manual/5.4/manual.html#4.4
+// "Internally, Lua uses the C longjmp facility to handle errors. (Lua will
+// use exceptions if you compile it as C++; search for LUAI_THROW in the
+// source code for details.)"
+// Some blocks within this function construct temporary C++ objects in the
+// expectation that these objects will be properly destroyed even if code
+// reached by that block raises a Lua error.
+LLSD lua_tollsd(lua_State* L, int index)
+{
+ switch (lua_type(L, index))
+ {
+ case LUA_TNONE:
+ // Should LUA_TNONE be an error instead of returning isUndefined()?
+ case LUA_TNIL:
+ return {};
+
+ case LUA_TBOOLEAN:
+ return bool(lua_toboolean(L, index));
+
+ case LUA_TNUMBER:
+ {
+ // Vanilla Lua supports lua_tointegerx(), which tells the caller
+ // whether the number at the specified stack index is or is not an
+ // integer. Apparently the function exists but does not work right in
+ // Luau: it reports even non-integer numbers as integers.
+ // Instead, check if integer truncation leaves the number intact.
+ lua_Number numval{ lua_tonumber(L, index) };
+ lua_Integer intval{ narrow(numval) };
+ if (lua_Number(intval) == numval)
+ {
+ return LLSD::Integer(intval);
+ }
+ else
+ {
+ return numval;
+ }
+ }
+
+ case LUA_TSTRING:
+ return lua_tostdstring(L, index);
+
+ case LUA_TUSERDATA:
+ {
+ LLSD::Binary binary(lua_rawlen(L, index));
+ std::memcpy(binary.data(), lua_touserdata(L, index), binary.size());
+ return binary;
+ }
+
+ case LUA_TTABLE:
+ {
+ // A Lua table correctly constructed to convert to LLSD will have
+ // either consecutive integer keys starting at 1, which we represent
+ // as an LLSD array (with Lua key 1 at C++ index 0), or will have
+ // all string keys.
+ //
+ // In the belief that Lua table traversal skips "holes," that is, it
+ // doesn't report any key/value pair whose value is nil, we allow a
+ // table with integer keys >= 1 but with "holes." This produces an
+ // LLSD array with isUndefined() entries at unspecified keys. There
+ // would be no other way for a Lua caller to construct an
+ // isUndefined() LLSD array entry. However, to guard against crazy int
+ // keys, we forbid gaps larger than a certain size: crazy int keys
+ // could result in a crazy large contiguous LLSD array.
+ //
+ // Possible looseness could include:
+ // - A mix of integer and string keys could produce an LLSD map in
+ // which the integer keys are converted to string. (Key conversion
+ // must be performed in C++, not Lua, to avoid confusing
+ // lua_next().)
+ // - However, since in Lua t[0] and t["0"] are distinct table entries,
+ // do not consider converting numeric string keys to int to return
+ // an LLSD array.
+ // But until we get more experience with actual Lua scripts in
+ // practice, let's say that any deviation is a Lua coding error.
+ // An important property of the strict definition above is that most
+ // conforming data blobs can make a round trip across the language
+ // boundary and still compare equal. A non-conforming data blob would
+ // lose that property.
+ // Known exceptions to round trip identity:
+ // - Empty LLSD map and empty LLSD array convert to empty Lua table.
+ // But empty Lua table converts to isUndefined() LLSD object.
+ // - LLSD::Real with integer value returns as LLSD::Integer.
+ // - LLSD::UUID, LLSD::Date and LLSD::URI all convert to Lua string,
+ // and so return as LLSD::String.
+ // - Lua does not store any table key whose value is nil. An LLSD
+ // array with isUndefined() entries produces a Lua table with
+ // "holes" in the int key sequence; this converts back to an LLSD
+ // array containing corresponding isUndefined() entries -- except
+ // when one or more of the final entries isUndefined(). These are
+ // simply dropped, producing a shorter LLSD array than the original.
+ // - For the same reason, any keys in an LLSD map whose value
+ // isUndefined() are simply discarded in the converted Lua table.
+ // This converts back to an LLSD map lacking those keys.
+ // - If it's important to preserve the original length of an LLSD
+ // array whose final entries are undefined, or the full set of keys
+ // for an LLSD map some of whose values are undefined, store an
+ // LLSD::emptyArray() or emptyMap() instead. These will be
+ // represented in Lua as empty table, which should convert back to
+ // undefined LLSD. Naturally, though, those won't survive a second
+ // round trip.
+
+ // This is the most important of the luaL_checkstack() calls because a
+ // deeply nested Lua structure will enter this case at each level, and
+ // we'll need another 2 stack slots to traverse each nested table.
+ luaL_checkstack(L, 2, nullptr);
+ // BEFORE we push nil to initialize the lua_next() traversal, convert
+ // 'index' to absolute! Our caller might have passed a relative index;
+ // we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we
+ // push nil, what we find at index -1 is nil, not the table!
+ index = lua_absindex(L, index);
+ lua_pushnil(L); // first key
+ if (! lua_next(L, index))
+ {
+ // it's a table, but the table is empty -- no idea if it should be
+ // modeled as empty array or empty map -- return isUndefined(),
+ // which can be consumed as either
+ return {};
+ }
+ // key is at stack index -2, value at index -1
+ // from here until lua_next() returns 0, have to lua_pop(2) if we
+ // return early
+ LuaPopper popper(L, 2);
+ // Remember the type of the first key
+ auto firstkeytype{ lua_type(L, -2) };
+ switch (firstkeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ // First Lua key is a number: try to convert table to LLSD array.
+ // This is tricky because we don't know in advance the size of the
+ // array. The Lua reference manual says that lua_rawlen() is the
+ // same as the length operator '#'; but the length operator states
+ // that it might stop at any "hole" in the subject table.
+ // Moreover, the Lua next() function (and presumably lua_next())
+ // traverses a table in unspecified order, even for numeric keys
+ // (emphasized in the doc).
+ // Make a preliminary pass over the whole table to validate and to
+ // collect keys.
+ std::vector<LLSD::Integer> keys;
+ // Try to determine the length of the table. If the length
+ // operator is truthful, avoid allocations while we grow the keys
+ // vector. Even if it's not, we can still grow the vector, albeit
+ // a little less efficiently.
+ keys.reserve(lua_objlen(L, index));
+ do
+ {
+ auto arraykeytype{ lua_type(L, -2) };
+ switch (arraykeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ int isint;
+ lua_Integer intkey{ lua_tointegerx(L, -2, &isint) };
+ if (! isint)
+ {
+ // key isn't an integer - this doesn't fit our LLSD
+ // array constraints
+ return lluau::error(L, "Expected integer array key, got %f instead",
+ lua_tonumber(L, -2));
+ }
+ if (intkey < 1)
+ {
+ return lluau::error(L, "array key %d out of bounds", int(intkey));
+ }
+
+ keys.push_back(LLSD::Integer(intkey));
+ break;
+ }
+
+ case LUA_TSTRING:
+ // break out strings specially to report the value
+ return lluau::error(L, "Cannot convert string array key '%s' to LLSD",
+ lua_tostring(L, -2));
+
+ default:
+ return lluau::error(L, "Cannot convert %s array key to LLSD",
+ lua_typename(L, arraykeytype));
+ }
+
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ // Table keys are all integers: are they reasonable integers?
+ // Arbitrary max: may bite us, but more likely to protect us
+ const size_t array_max{ 10000 };
+ if (keys.size() > array_max)
+ {
+ return lluau::error(L, "Conversion from Lua to LLSD array limited to %d entries",
+ int(array_max));
+ }
+ // We know the smallest key is >= 1. Check the largest. We also
+ // know the vector is NOT empty, else we wouldn't have gotten here.
+ std::sort(keys.begin(), keys.end());
+ LLSD::Integer highkey = *keys.rbegin();
+ if ((highkey - LLSD::Integer(keys.size())) > 100)
+ {
+ // Looks like we've gone beyond intentional array gaps into
+ // crazy key territory.
+ return lluau::error(L, "Gaps in Lua table too large for conversion to LLSD array");
+ }
+ // right away expand the result array to the size we'll need
+ LLSD result{ LLSD::emptyArray() };
+ result[highkey - 1] = LLSD();
+ // Traverse the table again, and this time populate result array.
+ lua_pushnil(L); // first key
+ while (lua_next(L, index))
+ {
+ // key at stack index -2, value at index -1
+ // We've already validated lua_tointegerx() for each key.
+ auto key{ lua_tointeger(L, -2) };
+ // Don't forget to subtract 1 from Lua key for LLSD subscript!
+ result[LLSD::Integer(key) - 1] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ }
+ return result;
+ }
+
+ case LUA_TSTRING:
+ {
+ // First Lua key is a string: try to convert table to LLSD map
+ LLSD result{ LLSD::emptyMap() };
+ do
+ {
+ auto mapkeytype{ lua_type(L, -2) };
+ if (mapkeytype != LUA_TSTRING)
+ {
+ return lluau::error(L, "Cannot convert %s map key to LLSD",
+ lua_typename(L, mapkeytype));
+ }
+
+ auto key{ lua_tostdstring(L, -2) };
+ result[key] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ return result;
+ }
+
+ default:
+ // First Lua key isn't number or string: sorry
+ return lluau::error(L, "Cannot convert %s table key to LLSD",
+ lua_typename(L, firstkeytype));
+ }
+ }
+
+ default:
+ // Other Lua entities (e.g. function, C function, light userdata,
+ // thread, userdata) are not convertible to LLSD, indicating a coding
+ // error in the caller.
+ return lluau::error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index));
+ }
+}
+
+// By analogy with existing lua_pushmumble() functions, push onto state L's
+// stack a Lua object corresponding to the passed LLSD object.
+void lua_pushllsd(lua_State* L, const LLSD& data)
+{
+ // might need 2 slots for array or map
+ luaL_checkstack(L, 2, nullptr);
+ switch (data.type())
+ {
+ case LLSD::TypeUndefined:
+ lua_pushnil(L);
+ break;
+
+ case LLSD::TypeBoolean:
+ lua_pushboolean(L, data.asBoolean());
+ break;
+
+ case LLSD::TypeInteger:
+ lua_pushinteger(L, data.asInteger());
+ break;
+
+ case LLSD::TypeReal:
+ lua_pushnumber(L, data.asReal());
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ auto binary{ data.asBinary() };
+ std::memcpy(lua_newuserdata(L, binary.size()),
+ binary.data(), binary.size());
+ break;
+ }
+
+ case LLSD::TypeMap:
+ {
+ // push a new table with space for our non-array keys
+ lua_createtable(L, 0, data.size());
+ for (const auto& pair: llsd::inMap(data))
+ {
+ // push value -- so now table is at -2, value at -1
+ lua_pushllsd(L, pair.second);
+ // pop value, assign to table[key]
+ lua_setfield(L, -2, pair.first.c_str());
+ }
+ break;
+ }
+
+ case LLSD::TypeArray:
+ {
+ // push a new table with space for array entries
+ lua_createtable(L, data.size(), 0);
+ lua_Integer key{ 0 };
+ for (const auto& item: llsd::inArray(data))
+ {
+ // push new array value: table at -2, value at -1
+ lua_pushllsd(L, item);
+ // pop value, assign table[key] = value
+ lua_rawseti(L, -2, ++key);
+ }
+ break;
+ }
+
+ case LLSD::TypeString:
+ case LLSD::TypeUUID:
+ case LLSD::TypeDate:
+ case LLSD::TypeURI:
+ default:
+ {
+ lua_pushstdstring(L, data.asString());
+ break;
+ }
+ }
+}
+
+/*****************************************************************************
+* LuaState class
+*****************************************************************************/
+LuaState::LuaState(script_finished_fn cb):
+ mCallback(cb),
+ mState(nullptr)
+{
+ initLuaState();
+}
+
+void LuaState::initLuaState()
+{
+ if (mState)
+ {
+ lua_close(mState);
+ }
+ mState = luaL_newstate();
+ luaL_openlibs(mState);
+ LuaFunction::init(mState);
+ // Try to make print() write to our log.
+ lua_register(mState, "print", LuaFunction::get("print_info"));
+ // We don't want to have to prefix require().
+ lua_register(mState, "require", LuaFunction::get("require"));
+}
+
+LuaState::~LuaState()
+{
+ // Did somebody call obtainListener() on this LuaState?
+ // That is, is there a LuaListener key in its registry?
+ LuaListener::destruct(getListener());
+
+ lua_close(mState);
+
+ if (mCallback)
+ {
+ // mError potentially set by previous checkLua() call(s)
+ mCallback(mError);
+ }
+}
+
+bool LuaState::checkLua(const std::string& desc, int r)
+{
+ if (r != LUA_OK)
+ {
+ mError = lua_tostring(mState, -1);
+ lua_pop(mState, 1);
+
+ LL_WARNS() << desc << ": " << mError << LL_ENDL;
+ return false;
+ }
+ return true;
+}
+
+std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text)
+{
+ lluau::set_interrupts_counter(mState, 0);
+
+ lua_callbacks(mState)->interrupt = [](lua_State *L, int gc)
+ {
+ // skip if we're interrupting only for garbage collection
+ if (gc >= 0)
+ return;
+
+ LLCoros::checkStop();
+ lluau::check_interrupts_counter(L);
+ };
+
+ if (! checkLua(desc, lluau::dostring(mState, desc, text)))
+ {
+ LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL;
+ return { -1, mError };
+ }
+
+ // here we believe there was no error -- did the Lua fragment leave
+ // anything on the stack?
+ std::pair<int, LLSD> result{ lua_gettop(mState), {} };
+ LL_INFOS("Lua") << desc << " done, " << result.first << " results." << LL_ENDL;
+ if (result.first)
+ {
+ // aha, at least one entry on the stack!
+ if (result.first == 1)
+ {
+ // Don't forget that lua_tollsd() can throw Lua errors.
+ try
+ {
+ result.second = lua_tollsd(mState, 1);
+ }
+ catch (const std::exception& error)
+ {
+ LL_WARNS("Lua") << desc << " error converting result: " << error.what() << LL_ENDL;
+ // lua_tollsd() is designed to be called from a lua_function(),
+ // that is, from a C++ function called by Lua. In case of error,
+ // it throws a Lua error to be caught by the Lua runtime. expr()
+ // is a peculiar use case in which our C++ code is calling
+ // lua_tollsd() after return from the Lua runtime. We must catch
+ // the exception thrown for a Lua error, else it will propagate
+ // out to the main coroutine and terminate the viewer -- but since
+ // we instead of the Lua runtime catch it, our lua_State retains
+ // its internal error status. Any subsequent lua_pcall() calls
+ // with this lua_State will report error regardless of whether the
+ // chunk runs successfully. Get a new lua_State().
+ initLuaState();
+ return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ }
+ }
+ else
+ {
+ // multiple entries on the stack
+ int index;
+ try
+ {
+ for (index = 1; index <= result.first; ++index)
+ {
+ result.second.append(lua_tollsd(mState, index));
+ }
+ }
+ catch (const std::exception& error)
+ {
+ LL_WARNS("Lua") << desc << " error converting result " << index << ": "
+ << error.what() << LL_ENDL;
+ // see above comments regarding lua_State's error status
+ initLuaState();
+ return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
+ }
+ }
+ }
+ // pop everything
+ lua_settop(mState, 0);
+
+ // If we ran a script that loaded the fiber module, finish up with a call
+ // to fiber.run(). That allows a script to kick off some number of fibers,
+ // do some work on the main thread and then fall off the end of the script
+ // without explicitly appending a call to fiber.run(). run() ensures the
+ // rest of the fibers run to completion (or error).
+ luaL_checkstack(mState, 4, nullptr);
+ // Push _MODULES table on stack
+ luaL_findtable(mState, LUA_REGISTRYINDEX, "_MODULES", 1);
+ int index = lua_gettop(mState);
+ bool found = false;
+ // Did this chunk already require('fiber')? To find out, we must search
+ // the _MODULES table, because our require() implementation uses the
+ // pathname of the module file as the key. Push nil key to start.
+ lua_pushnil(mState);
+ while (lua_next(mState, index) != 0)
+ {
+ // key is at index -2, value at index -1
+ // "While traversing a table, do not call lua_tolstring directly on a
+ // key, unless you know that the key is actually a string. Recall that
+ // lua_tolstring changes the value at the given index; this confuses
+ // the next call to lua_next."
+ // https://www.lua.org/manual/5.1/manual.html#lua_next
+ if (lua_type(mState, -2) == LUA_TSTRING &&
+ fsyspath(lua_tostdstring(mState, -2)).stem() == "fiber")
+ {
+ found = true;
+ break;
+ }
+ // pop value so key is at top for lua_next()
+ lua_pop(mState, 1);
+ }
+ if (found)
+ {
+ // okay, index -1 is a table loaded from a file 'fiber.xxx' --
+ // does it have a function named 'run'?
+ auto run_type{ lua_getfield(mState, -1, "run") };
+ if (run_type == LUA_TFUNCTION)
+ {
+ // there's a fiber.run() function sitting on the top of the stack
+ // -- call it with no arguments, discarding anything it returns
+ LL_INFOS("Lua") << desc << " p.s. fiber.run()" << LL_ENDL;
+ if (! checkLua(desc, lua_pcall(mState, 0, 0, 0)))
+ {
+ LL_WARNS("Lua") << desc << " p.s. fiber.run() error: " << mError << LL_ENDL;
+ return { -1, mError };
+ }
+ LL_INFOS("Lua") << desc << " p.s. done." << LL_ENDL;
+ }
+ }
+ // pop everything again
+ lua_settop(mState, 0);
+ return result;
+}
+
+LuaListener::ptr_t LuaState::getListener(lua_State* L)
+{
+ // have to use one more stack slot
+ luaL_checkstack(L, 1, nullptr);
+ LuaListener::ptr_t listener;
+ // Does this lua_State already have a LuaListener stored in the registry?
+ auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") };
+ llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do already have a LuaListener. Retrieve it.
+ int isint;
+ listener = LuaListener::getInstance(lua_tointegerx(L, -1, &isint));
+ // Nobody should have destroyed this LuaListener instance!
+ llassert(isint && listener);
+ }
+ // pop the int "event.listener" key
+ lua_pop(L, 1);
+ return listener;
+}
+
+LuaListener::ptr_t LuaState::obtainListener(lua_State* L)
+{
+ auto listener{ getListener(L) };
+ if (! listener)
+ {
+ // have to use one more stack slot
+ luaL_checkstack(L, 1, nullptr);
+ // instantiate a new LuaListener, binding the L state -- but use a
+ // no-op deleter: we do NOT want this ptr_t to manage the lifespan of
+ // this new LuaListener!
+ listener.reset(new LuaListener(L), [](LuaListener*){});
+ // set its key in the field where we'll look for it later
+ lua_pushinteger(L, listener->getKey());
+ lua_setfield(L, LUA_REGISTRYINDEX, "event.listener");
+ }
+ return listener;
+}
+
+/*****************************************************************************
+* LuaPopper class
+*****************************************************************************/
+LuaPopper::~LuaPopper()
+{
+ if (mCount)
+ {
+ lua_pop(mState, mCount);
+ }
+}
+
+/*****************************************************************************
+* LuaFunction class
+*****************************************************************************/
+LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function,
+ const std::string_view& helptext)
+{
+ const auto& [registry, lookup] = getState();
+ registry.emplace(name, Registry::mapped_type{ function, helptext });
+ lookup.emplace(function, name);
+}
+
+void LuaFunction::init(lua_State* L)
+{
+ const auto& [registry, lookup] = getRState();
+ luaL_checkstack(L, 2, nullptr);
+ // create LL table --
+ // it happens that we know exactly how many non-array members we want
+ lua_createtable(L, 0, int(narrow(lookup.size())));
+ int idx = lua_gettop(L);
+ for (const auto& [name, pair]: registry)
+ {
+ const auto& [funcptr, helptext] = pair;
+ // store funcptr in LL table with saved name
+ lua_pushcfunction(L, funcptr, name.c_str());
+ lua_setfield(L, idx, name.c_str());
+ }
+ // store LL in new lua_State's globals
+ lua_setglobal(L, "LL");
+}
+
+lua_CFunction LuaFunction::get(const std::string& key)
+{
+ // use find() instead of subscripting to avoid creating an entry for
+ // unknown key
+ const auto& [registry, lookup] = getState();
+ auto found{ registry.find(key) };
+ return (found == registry.end())? nullptr : found->second.first;
+}
+
+std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
+{
+ // use function-local statics to ensure they're initialized
+ static Registry registry;
+ static Lookup lookup;
+ return { registry, lookup };
+}
+
+/*****************************************************************************
+* source_path()
+*****************************************************************************/
+lua_function(source_path, "return the source path of the running Lua script")
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushstdstring(L, lluau::source_path(L).u8string());
+ return 1;
+}
+
+/*****************************************************************************
+* source_dir()
+*****************************************************************************/
+lua_function(source_dir, "return the source directory of the running Lua script")
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string());
+ return 1;
+}
+
+/*****************************************************************************
+* abspath()
+*****************************************************************************/
+lua_function(abspath,
+ "for given filesystem path relative to running script, return absolute path")
+{
+ auto path{ lua_tostdstring(L, 1) };
+ lua_pop(L, 1);
+ lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string());
+ return 1;
+}
+
+/*****************************************************************************
+* check_stop()
+*****************************************************************************/
+lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown")
+{
+ LLCoros::checkStop();
+ return 0;
+}
+
+/*****************************************************************************
+* help()
+*****************************************************************************/
+lua_function(help,
+ "help(): list viewer's Lua functions\n"
+ "help(function): show help string for specific function")
+{
+ auto& luapump{ LLEventPumps::instance().obtain("lua output") };
+ const auto& [registry, lookup]{ LuaFunction::getRState() };
+ if (! lua_gettop(L))
+ {
+ // no arguments passed: list all lua_functions
+ for (const auto& [name, pair] : registry)
+ {
+ const auto& [fptr, helptext] = pair;
+ luapump.post(helptext);
+ }
+ }
+ else
+ {
+ // arguments passed: list each of the specified lua_functions
+ for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx)
+ {
+ std::string arg{ stringize("<unknown ", lua_typename(L, lua_type(L, idx)), ">") };
+ if (lua_type(L, idx) == LUA_TSTRING)
+ {
+ arg = lua_tostdstring(L, idx);
+ }
+ else if (lua_type(L, idx) == LUA_TFUNCTION)
+ {
+ // Caller passed the actual function instead of its string
+ // name. A Lua function is an anonymous callable object; it
+ // has a name only by assigment. You can't ask Lua for a
+ // function's name, which is why our constructor maintains a
+ // reverse Lookup map.
+ auto function{ lua_tocfunction(L, idx) };
+ if (auto found = lookup.find(function); found != lookup.end())
+ {
+ // okay, pass found name to lookup below
+ arg = found->second;
+ }
+ }
+
+ if (auto found = registry.find(arg); found != registry.end())
+ {
+ luapump.post(found->second.second);
+ }
+ else
+ {
+ luapump.post(arg + ": NOT FOUND");
+ }
+ }
+ // pop all arguments
+ lua_settop(L, 0);
+ }
+ return 0; // void return
+}
+
+/*****************************************************************************
+* leaphelp()
+*****************************************************************************/
+lua_function(
+ leaphelp,
+ "leaphelp(): list viewer's LEAP APIs\n"
+ "leaphelp(api): show help for specific api string name")
+{
+ LLSD request;
+ int top{ lua_gettop(L) };
+ if (top)
+ {
+ request = llsd::map("op", "getAPI", "api", lua_tostdstring(L, 1));
+ }
+ else
+ {
+ request = llsd::map("op", "getAPIs");
+ }
+ // pop all args
+ lua_settop(L, 0);
+
+ auto& outpump{ LLEventPumps::instance().obtain("lua output") };
+ auto listener{ LuaState::obtainListener(L) };
+ LLEventStream replyPump("leaphelp", true);
+ // ask the LuaListener's LeapListener and suspend calling coroutine until reply
+ auto reply{ llcoro::postAndSuspend(request, listener->getCommandName(), replyPump, "reply") };
+ reply.erase("reqid");
+
+ if (auto error = reply["error"]; error.isString())
+ {
+ outpump.post(error.asString());
+ return 0;
+ }
+
+ if (top)
+ {
+ // caller wants a specific API
+ outpump.post(stringize(reply["name"].asString(), ":\n", reply["desc"].asString()));
+ for (const auto& opmap : llsd::inArray(reply["ops"]))
+ {
+ std::ostringstream reqstr;
+ auto req{ opmap["required"] };
+ if (req.isArray())
+ {
+ const char* sep = " (requires ";
+ for (const auto& [reqkey, reqval] : llsd::inMap(req))
+ {
+ reqstr << sep << reqkey;
+ sep = ", ";
+ }
+ reqstr << ")";
+ }
+ outpump.post(stringize("---- ", reply["key"].asString(), " == '",
+ opmap["name"].asString(), "'", reqstr.str(), ":\n",
+ opmap["desc"].asString()));
+ }
+ }
+ else
+ {
+ // caller wants a list of APIs
+ for (const auto& [name, data] : llsd::inMap(reply))
+ {
+ outpump.post(stringize("==== ", name, ":\n", data["desc"].asString()));
+ }
+ }
+ return 0; // void return
+}
+
+/*****************************************************************************
+* lua_what
+*****************************************************************************/
+std::ostream& operator<<(std::ostream& out, const lua_what& self)
+{
+ switch (lua_type(self.L, self.index))
+ {
+ case LUA_TNONE:
+ // distinguish acceptable but non-valid index
+ out << "none";
+ break;
+
+ case LUA_TNIL:
+ out << "nil";
+ break;
+
+ case LUA_TBOOLEAN:
+ {
+ auto oldflags { out.flags() };
+ out << std::boolalpha << lua_toboolean(self.L, self.index);
+ out.flags(oldflags);
+ break;
+ }
+
+ case LUA_TNUMBER:
+ out << lua_tonumber(self.L, self.index);
+ break;
+
+ case LUA_TSTRING:
+ out << std::quoted(lua_tostdstring(self.L, self.index));
+ break;
+
+ case LUA_TUSERDATA:
+ {
+ const S32 maxlen = 20;
+ S32 binlen{ lua_rawlen(self.L, self.index) };
+ LLSD::Binary binary(std::min(maxlen, binlen));
+ std::memcpy(binary.data(), lua_touserdata(self.L, self.index), binary.size());
+ out << LL::hexdump(binary);
+ if (binlen > maxlen)
+ {
+ out << "...(" << (binlen - maxlen) << " more)";
+ }
+ break;
+ }
+
+ case LUA_TLIGHTUSERDATA:
+ out << lua_touserdata(self.L, self.index);
+ break;
+
+ default:
+ // anything else, don't bother trying to report value, just type
+ out << lua_typename(self.L, lua_type(self.L, self.index));
+ break;
+ }
+ return out;
+}
+
+/*****************************************************************************
+* lua_stack
+*****************************************************************************/
+std::ostream& operator<<(std::ostream& out, const lua_stack& self)
+{
+ out << "stack: [";
+ const char* sep = "";
+ for (int index = 1; index <= lua_gettop(self.L); ++index)
+ {
+ out << sep << lua_what(self.L, index);
+ sep = ", ";
+ }
+ out << ']';
+ return out;
+}
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
new file mode 100644
index 0000000000..e7013f92c6
--- /dev/null
+++ b/indra/llcommon/lua_function.h
@@ -0,0 +1,259 @@
+/**
+ * @file lua_function.h
+ * @author Nat Goodspeed
+ * @date 2024-02-05
+ * @brief Definitions useful for coding a new Luau entry point into C++
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LUA_FUNCTION_H)
+#define LL_LUA_FUNCTION_H
+
+#include "luau/luacode.h"
+#include "luau/lua.h"
+#include "luau/luaconf.h"
+#include "luau/lualib.h"
+#include "fsyspath.h"
+#include "stringize.h"
+#include <exception> // std::uncaught_exceptions()
+#include <memory> // std::shared_ptr
+#include <utility> // std::pair
+
+class LuaListener;
+
+namespace lluau
+{
+ // luau defines luaL_error() as void, but we want to use the Lua idiom of
+ // 'return error(...)'. Wrap luaL_error() in an int function.
+#if __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-security"
+#endif // __clang__
+ template<typename... Args>
+ int error(lua_State* L, const char* format, Args&&... args)
+ {
+ luaL_error(L, format, std::forward<Args>(args)...);
+#ifndef LL_MSVC
+ return 0;
+#endif
+ }
+#if __clang__
+#pragma clang diagnostic pop
+#endif // __clang__
+
+ // luau removed lua_dostring(), but since we perform the equivalent luau
+ // sequence in multiple places, encapsulate it. desc and text are strings
+ // rather than string_views because dostring() needs pointers to nul-
+ // terminated char arrays.
+ int dostring(lua_State* L, const std::string& desc, const std::string& text);
+ int loadstring(lua_State* L, const std::string& desc, const std::string& text);
+
+ fsyspath source_path(lua_State* L);
+
+ void set_interrupts_counter(lua_State *L, S32 counter);
+ void check_interrupts_counter(lua_State* L);
+} // namespace lluau
+
+std::string lua_tostdstring(lua_State* L, int index);
+void lua_pushstdstring(lua_State* L, const std::string& str);
+LLSD lua_tollsd(lua_State* L, int index);
+void lua_pushllsd(lua_State* L, const LLSD& data);
+
+/**
+ * RAII class to manage the lifespan of a lua_State
+ */
+class LuaState
+{
+public:
+ typedef std::function<void(std::string msg)> script_finished_fn;
+
+ LuaState(script_finished_fn cb={});
+
+ LuaState(const LuaState&) = delete;
+ LuaState& operator=(const LuaState&) = delete;
+
+ ~LuaState();
+
+ void initLuaState();
+
+ bool checkLua(const std::string& desc, int r);
+
+ // expr() is for when we want to capture any results left on the stack
+ // by a Lua expression, possibly including multiple return values.
+ // int < 0 means error, and LLSD::asString() is the error message.
+ // int == 0 with LLSD::isUndefined() means the Lua expression returned no
+ // results.
+ // int == 1 means the Lua expression returned one result.
+ // int > 1 with LLSD::isArray() means the Lua expression returned
+ // multiple results, represented as the entries of the array.
+ std::pair<int, LLSD> expr(const std::string& desc, const std::string& text);
+
+ operator lua_State*() const { return mState; }
+
+ // Return LuaListener for this LuaState if we already have one, else empty
+ // shared_ptr.
+ std::shared_ptr<LuaListener> getListener() { return getListener(mState); }
+ // Find or create LuaListener for this LuaState, returning its ptr_t.
+ std::shared_ptr<LuaListener> obtainListener() { return obtainListener(mState); }
+ // Return LuaListener for passed lua_State if we already have one, else
+ // empty shared_ptr.
+ static std::shared_ptr<LuaListener> getListener(lua_State* L);
+ // Find or create LuaListener for passed lua_State, returning its ptr_t.
+ static std::shared_ptr<LuaListener> obtainListener(lua_State* L);
+
+private:
+ script_finished_fn mCallback;
+ lua_State* mState;
+ std::string mError;
+};
+
+/**
+ * LuaPopper is an RAII struct whose role is to pop some number of entries
+ * from the Lua stack if the calling function exits early.
+ */
+struct LuaPopper
+{
+ LuaPopper(lua_State* L, int count):
+ mState(L),
+ mCount(count)
+ {}
+
+ LuaPopper(const LuaPopper&) = delete;
+ LuaPopper& operator=(const LuaPopper&) = delete;
+
+ ~LuaPopper();
+
+ void disarm() { set(0); }
+ void set(int count) { mCount = count; }
+
+ lua_State* mState;
+ int mCount;
+};
+
+/**
+ * LuaFunction is a base class containing a static registry of its static
+ * subclass call() methods. call() is NOT virtual: instead, each subclass
+ * constructor passes a pointer to its distinct call() method to the base-
+ * class constructor, along with a name by which to register that method.
+ *
+ * The init() method walks the registry and registers each such name with the
+ * passed lua_State.
+ */
+class LuaFunction
+{
+public:
+ LuaFunction(const std::string_view& name, lua_CFunction function,
+ const std::string_view& helptext);
+
+ static void init(lua_State* L);
+
+ static lua_CFunction get(const std::string& key);
+
+protected:
+ using Registry = std::map<std::string, std::pair<lua_CFunction, std::string>>;
+ using Lookup = std::map<lua_CFunction, std::string>;
+ static std::pair<const Registry&, const Lookup&> getRState() { return getState(); }
+
+private:
+ static std::pair<Registry&, Lookup&> getState();
+};
+
+/**
+ * lua_function(name, helptext) is a macro to facilitate defining C++ functions
+ * available to Lua. It defines a subclass of LuaFunction and declares a
+ * static instance of that subclass, thereby forcing the compiler to call its
+ * constructor at module initialization time. The constructor passes the
+ * stringized instance name to its LuaFunction base-class constructor, along
+ * with a pointer to the static subclass call() method. It then emits the
+ * call() method definition header, to be followed by a method body enclosed
+ * in curly braces as usual.
+ */
+#define lua_function(name, helptext) \
+static struct name##_luasub : public LuaFunction \
+{ \
+ name##_luasub(): LuaFunction(#name, &call, helptext) {} \
+ static int call(lua_State* L); \
+} name##_lua; \
+int name##_luasub::call(lua_State* L)
+// {
+// ... supply method body here, referencing 'L' ...
+// }
+
+// Usage: std::cout << lua_what(L, stackindex) << ...;
+// Reports on the Lua value found at the passed stackindex.
+// If cast to std::string, returns the corresponding string value.
+class lua_what
+{
+public:
+ lua_what(lua_State* state, int idx):
+ L(state),
+ index(idx)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const lua_what& self);
+
+ operator std::string() const { return stringize(*this); }
+
+private:
+ lua_State* L;
+ int index;
+};
+
+// Usage: std::cout << lua_stack(L) << ...;
+// Reports on the contents of the Lua stack.
+// If cast to std::string, returns the corresponding string value.
+class lua_stack
+{
+public:
+ lua_stack(lua_State* state):
+ L(state)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const lua_stack& self);
+
+ operator std::string() const { return stringize(*this); }
+
+private:
+ lua_State* L;
+};
+
+// adapted from indra/test/debug.h
+// can't generalize Debug::operator() target because it's a variadic template
+class LuaLog
+{
+public:
+ template <typename... ARGS>
+ LuaLog(lua_State* L, ARGS&&... args):
+ L(L),
+ mBlock(stringize(std::forward<ARGS>(args)...))
+ {
+ (*this)("entry ", lua_stack(L));
+ }
+
+ // non-copyable
+ LuaLog(const LuaLog&) = delete;
+ LuaLog& operator=(const LuaLog&) = delete;
+
+ ~LuaLog()
+ {
+ auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" };
+ (*this)(exceptional, "exit ", lua_stack(L));
+ }
+
+ template <typename... ARGS>
+ void operator()(ARGS&&... args)
+ {
+ LL_INFOS("Lua") << mBlock << ' ';
+ stream_to(LL_CONT, std::forward<ARGS>(args)...);
+ LL_ENDL;
+ }
+
+private:
+ lua_State* L;
+ const std::string mBlock;
+};
+
+#endif /* ! defined(LL_LUA_FUNCTION_H) */
diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp
new file mode 100644
index 0000000000..5c4989e891
--- /dev/null
+++ b/indra/llcommon/lualistener.cpp
@@ -0,0 +1,114 @@
+/**
+ * @file lualistener.cpp
+ * @author Nat Goodspeed
+ * @date 2024-02-06
+ * @brief Implementation for lualistener.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lualistener.h"
+// STL headers
+// std headers
+#include <cstdlib> // std::rand()
+#include <cstring> // std::memcpy()
+// external library headers
+#include "luau/lua.h"
+// other Linden headers
+#include "llerror.h"
+#include "llleaplistener.h"
+#include "lua_function.h"
+
+const int MAX_QSIZE = 1000;
+
+std::ostream& operator<<(std::ostream& out, const LuaListener& self)
+{
+ return out << "LuaListener(" << self.getReplyName() << ", " << self.getCommandName() << ")";
+}
+
+LuaListener::LuaListener(lua_State* L):
+ super(getUniqueKey()),
+ mCoroName(LLCoros::getName()),
+ mListener(new LLLeapListener(
+ "LuaListener",
+ [this](const std::string& pump, const LLSD& data)
+ { return queueEvent(pump, data); })),
+ // Listen for shutdown events.
+ mShutdownConnection(
+ LLCoros::getStopListener(
+ "LuaState",
+ mCoroName,
+ [this](const LLSD&)
+ {
+ // If a Lua script is still blocked in getNext() during
+ // viewer shutdown, close the queue to wake up getNext().
+ mQueue.close();
+ }))
+{}
+
+LuaListener::~LuaListener()
+{}
+
+int LuaListener::getUniqueKey()
+{
+ // Find a random key that does NOT already correspond to a LuaListener
+ // instance. Passing a duplicate key to LLInstanceTracker would do Bad
+ // Things.
+ int key;
+ do
+ {
+ key = std::rand();
+ } while (LuaListener::getInstance(key));
+ // This is theoretically racy, if we were instantiating new
+ // LuaListeners on multiple threads. Don't.
+ return key;
+}
+
+std::string LuaListener::getReplyName() const
+{
+ return mListener->getReplyPump().getName();
+}
+
+std::string LuaListener::getCommandName() const
+{
+ return mListener->getPumpName();
+}
+
+bool LuaListener::queueEvent(const std::string& pump, const LLSD& data)
+{
+ // Our Lua script might be stalled, or just fail to retrieve events. Don't
+ // grow this queue indefinitely. But don't set MAX_QSIZE as the queue
+ // capacity or we'd block the post() call trying to propagate this event!
+ if (auto size = mQueue.size(); size > MAX_QSIZE)
+ {
+ LL_WARNS("Lua") << "LuaListener queue for " << getReplyName()
+ << " exceeds " << MAX_QSIZE << ": " << size
+ << " -- discarding event" << LL_ENDL;
+ }
+ else
+ {
+ mQueue.push(decltype(mQueue)::value_type(pump, data));
+ }
+ return false;
+}
+
+LuaListener::PumpData LuaListener::getNext()
+{
+ try
+ {
+ LLCoros::TempStatus status("get_event_next()");
+ return mQueue.pop();
+ }
+ catch (const LLThreadSafeQueueInterrupt&)
+ {
+ // mQueue has been closed. The only way that happens is when we detect
+ // viewer shutdown. Terminate the calling coroutine.
+ LLCoros::checkStop();
+ return {};
+ }
+}
diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h
new file mode 100644
index 0000000000..85fb093cd6
--- /dev/null
+++ b/indra/llcommon/lualistener.h
@@ -0,0 +1,81 @@
+/**
+ * @file lualistener.h
+ * @author Nat Goodspeed
+ * @date 2024-02-06
+ * @brief Define LuaListener class
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LUALISTENER_H)
+#define LL_LUALISTENER_H
+
+#include "llevents.h"
+#include "llinstancetracker.h"
+#include "llsd.h"
+#include "llthreadsafequeue.h"
+#include <iosfwd> // std::ostream
+#include <memory> // std::unique_ptr
+#include <string>
+#include <utility> // std::pair
+
+struct lua_State;
+class LLLeapListener;
+
+/**
+ * LuaListener is based on LLLeap. It serves an analogous function.
+ *
+ * Each LuaListener instance has an int key, generated randomly to
+ * inconvenience malicious Lua scripts wanting to mess with others. The idea
+ * is that a given lua_State stores in its Registry:
+ * - "event.listener": the int key of the corresponding LuaListener, if any
+ * The original thought was that LuaListener would itself store the Lua
+ * function -- but surprisingly, there is no C/C++ type in the API that stores
+ * a Lua function.
+ *
+ * (We considered storing in "event.listener" the LuaListener pointer itself
+ * as a light userdata, but the problem would be if Lua code overwrote that.
+ * We want to prevent any Lua script from crashing the viewer, intentionally
+ * or otherwise. Safer to use a key lookup.)
+ *
+ * Like LLLeap, each LuaListener instance also has an associated
+ * LLLeapListener to respond to LLEventPump management commands.
+ */
+class LuaListener: public LLInstanceTracker<LuaListener, int>
+{
+ using super = LLInstanceTracker<LuaListener, int>;
+public:
+ LuaListener(lua_State* L);
+
+ LuaListener(const LuaListener&) = delete;
+ LuaListener& operator=(const LuaListener&) = delete;
+
+ ~LuaListener();
+
+ std::string getReplyName() const;
+ std::string getCommandName() const;
+
+ /**
+ * LuaListener enqueues reply events from its LLLeapListener on mQueue.
+ * Call getNext() to retrieve the next such event. Blocks the calling
+ * coroutine if the queue is empty.
+ */
+ using PumpData = std::pair<std::string, LLSD>;
+ PumpData getNext();
+
+ friend std::ostream& operator<<(std::ostream& out, const LuaListener& self);
+
+private:
+ static int getUniqueKey();
+ bool queueEvent(const std::string& pump, const LLSD& data);
+
+ LLThreadSafeQueue<PumpData> mQueue;
+
+ std::string mCoroName;
+ std::unique_ptr<LLLeapListener> mListener;
+ LLTempBoundListener mShutdownConnection;
+};
+
+#endif /* ! defined(LL_LUALISTENER_H) */
diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h
index 536a18abc1..63d44a7272 100644
--- a/indra/llcommon/stringize.h
+++ b/indra/llcommon/stringize.h
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2008-12-17
* @brief stringize(item) template function and STRINGIZE(expression) macro
- *
+ *
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -88,13 +88,13 @@ struct gstringize_impl
};
// partially specialize for a single STRING argument -
-// note that ll_convert<T>(T) already handles the trivial case
+// note that ll_convert_to<T>(T) already handles the trivial case
template <typename OUTCHAR, typename INCHAR>
struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>>
{
auto operator()(const std::basic_string<INCHAR>& arg)
{
- return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ return ll_convert_to<std::basic_string<OUTCHAR>>(arg);
}
};
@@ -105,7 +105,7 @@ struct gstringize_impl<OUTCHAR, INCHAR*>
{
auto operator()(const INCHAR* arg)
{
- return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ return ll_convert_to<std::basic_string<OUTCHAR>>(arg);
}
};
diff --git a/indra/llcommon/tests/StringVec.h b/indra/llcommon/tests/StringVec.h
index 4311cba992..761956a012 100644
--- a/indra/llcommon/tests/StringVec.h
+++ b/indra/llcommon/tests/StringVec.h
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2012-02-24
* @brief Extend TUT ensure_equals() to handle std::vector<std::string>
- *
+ *
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (c) 2012, Linden Research, Inc.
* $/LicenseInfo$
@@ -18,6 +18,16 @@
typedef std::vector<std::string> StringVec;
+#if defined(LL_LLTUT_H)
+// Modern compilers require us to define operator<<(std::ostream&, StringVec)
+// before the definition of the ensure() template that engages it. The error
+// stating that the compiler can't find a viable operator<<() is so perplexing
+// that even though I've obviously hit it a couple times before, a new
+// instance still caused much head-scratching. This warning is intended to
+// demystify any inadvertent future recurrence.
+#warning "StringVec.h must be #included BEFORE lltut.h for ensure() to work"
+#endif
+
std::ostream& operator<<(std::ostream& out, const StringVec& strings)
{
out << '(';
diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp
index 3ec429530c..63d5bdbb70 100644
--- a/indra/llcommon/tests/llerror_test.cpp
+++ b/indra/llcommon/tests/llerror_test.cpp
@@ -47,7 +47,7 @@ enum LogFieldIndex
MSG_FIELD
};
-static const char* FieldName[] =
+static const char* FieldName[] =
{
"TIME",
"LEVEL",
@@ -62,15 +62,15 @@ namespace
#ifdef __clang__
# pragma clang diagnostic ignored "-Wunused-function"
#endif
- void test_that_error_h_includes_enough_things_to_compile_a_message()
- {
- LL_INFOS() << "!" << LL_ENDL;
- }
+ void test_that_error_h_includes_enough_things_to_compile_a_message()
+ {
+ LL_INFOS() << "!" << LL_ENDL;
+ }
}
namespace
{
- static bool fatalWasCalled = false;
+ static bool fatalWasCalled = false;
struct FatalWasCalled: public std::runtime_error
{
FatalWasCalled(const std::string& what): std::runtime_error(what) {}
@@ -96,427 +96,465 @@ namespace
namespace tut
{
- class TestRecorder : public LLError::Recorder
- {
- public:
- TestRecorder()
- {
- showTime(false);
- }
- virtual ~TestRecorder()
- {}
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message)
- {
- mMessages.push_back(message);
- }
-
- int countMessages() { return (int) mMessages.size(); }
- void clearMessages() { mMessages.clear(); }
-
- std::string message(int n)
- {
- std::ostringstream test_name;
- test_name << "testing message " << n << ", not enough messages";
-
- tut::ensure(test_name.str(), n < countMessages());
- return mMessages[n];
- }
-
- private:
- typedef std::vector<std::string> MessageVector;
- MessageVector mMessages;
- };
-
- struct ErrorTestData
- {
- LLError::RecorderPtr mRecorder;
- LLError::SettingsStoragePtr mPriorErrorSettings;
-
- ErrorTestData():
- mRecorder(new TestRecorder())
- {
- fatalWasCalled = false;
-
- mPriorErrorSettings = LLError::saveAndResetSettings();
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- LLError::setFatalFunction(fatalCall);
- LLError::addRecorder(mRecorder);
- }
-
- ~ErrorTestData()
- {
- LLError::removeRecorder(mRecorder);
- LLError::restoreSettings(mPriorErrorSettings);
- }
-
- int countMessages()
- {
- return std::dynamic_pointer_cast<TestRecorder>(mRecorder)->countMessages();
- }
-
- void clearMessages()
- {
- std::dynamic_pointer_cast<TestRecorder>(mRecorder)->clearMessages();
- }
-
- void setWantsTime(bool t)
- {
- std::dynamic_pointer_cast<TestRecorder>(mRecorder)->showTime(t);
- }
-
- void setWantsMultiline(bool t)
- {
- std::dynamic_pointer_cast<TestRecorder>(mRecorder)->showMultiline(t);
- }
-
- std::string message(int n)
- {
- return std::dynamic_pointer_cast<TestRecorder>(mRecorder)->message(n);
- }
-
- void ensure_message_count(int expectedCount)
- {
- ensure_equals("message count", countMessages(), expectedCount);
- }
-
- std::string message_field(int msgnum, LogFieldIndex fieldnum)
- {
- std::ostringstream test_name;
- test_name << "testing message " << msgnum << ", not enough messages";
- tut::ensure(test_name.str(), msgnum < countMessages());
-
- std::string msg(message(msgnum));
-
- std::string field_value;
-
- // find the start of the field; fields are separated by a single space
- size_t scan = 0;
- int on_field = 0;
- while ( scan < msg.length() && on_field < fieldnum )
- {
- // fields are delimited by one space
- if ( ' ' == msg[scan] )
- {
- if ( on_field < FUNCTION_FIELD )
- {
- on_field++;
- }
- // except function, which may have embedded spaces so ends with " : "
- else if ( ( on_field == FUNCTION_FIELD )
- && ( ':' == msg[scan+1] && ' ' == msg[scan+2] )
- )
- {
- on_field++;
- scan +=2;
- }
- }
- scan++;
- }
- size_t start_field = scan;
- size_t fieldlen = 0;
- if ( fieldnum < FUNCTION_FIELD )
- {
- fieldlen = msg.find(' ', start_field) - start_field;
- }
- else if ( fieldnum == FUNCTION_FIELD )
- {
- fieldlen = msg.find(" : ", start_field) - start_field;
- }
- else if ( MSG_FIELD == fieldnum ) // no delimiter, just everything to the end
- {
- fieldlen = msg.length() - start_field;
- }
-
- return msg.substr(start_field, fieldlen);
- }
-
- void ensure_message_field_equals(int msgnum, LogFieldIndex fieldnum, const std::string& expectedText)
- {
- std::ostringstream test_name;
- test_name << "testing message " << msgnum << " field " << FieldName[fieldnum] << "\n message: \"" << message(msgnum) << "\"\n ";
-
- ensure_equals(test_name.str(), message_field(msgnum, fieldnum), expectedText);
- }
-
- void ensure_message_does_not_contain(int n, const std::string& expectedText)
- {
- std::ostringstream test_name;
- test_name << "testing message " << n;
-
- ensure_does_not_contain(test_name.str(), message(n), expectedText);
- }
- };
-
- typedef test_group<ErrorTestData> ErrorTestGroup;
- typedef ErrorTestGroup::object ErrorTestObject;
-
- ErrorTestGroup errorTestGroup("error");
-
- template<> template<>
- void ErrorTestObject::test<1>()
- // basic test of output
- {
- LL_INFOS() << "test" << LL_ENDL;
- LL_INFOS() << "bob" << LL_ENDL;
-
- ensure_message_field_equals(0, MSG_FIELD, "test");
- ensure_message_field_equals(1, MSG_FIELD, "bob");
- }
+ class TestRecorder : public LLError::Recorder
+ {
+ public:
+ TestRecorder()
+ {
+ showTime(false);
+ }
+ virtual ~TestRecorder()
+ {}
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message)
+ {
+ mMessages.push_back(message);
+ }
+
+ int countMessages() const { return (int) mMessages.size(); }
+ void clearMessages() { mMessages.clear(); }
+
+ std::string message(int n) const
+ {
+ std::ostringstream test_name;
+ test_name << "testing message " << n << ", not enough messages";
+
+ tut::ensure(test_name.str(), n < countMessages());
+ return mMessages[n];
+ }
+
+ void reportMessages() const
+ {
+ std::cerr << '\n';
+ int n = 0;
+ for (const auto& msg : mMessages)
+ {
+ std::cerr << std::setw(2) << n++ << ": " << msg.substr(0, 100) << '\n';
+ }
+ }
+
+ private:
+ typedef std::vector<std::string> MessageVector;
+ MessageVector mMessages;
+ };
+
+ struct ErrorTestData
+ {
+ LLError::RecorderPtr mRecorder;
+ LLError::SettingsStoragePtr mPriorErrorSettings;
+
+ auto recorder() { return std::dynamic_pointer_cast<TestRecorder>(mRecorder); }
+
+ ErrorTestData():
+ mRecorder(new TestRecorder())
+ {
+ fatalWasCalled = false;
+
+ mPriorErrorSettings = LLError::saveAndResetSettings();
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LLError::setFatalFunction(fatalCall);
+ LLError::addRecorder(mRecorder);
+ }
+
+ ~ErrorTestData()
+ {
+ LLError::removeRecorder(mRecorder);
+ LLError::restoreSettings(mPriorErrorSettings);
+ }
+
+ int countMessages()
+ {
+ return recorder()->countMessages();
+ }
+
+ void clearMessages()
+ {
+ recorder()->clearMessages();
+ }
+
+ void setWantsTime(bool t)
+ {
+ recorder()->showTime(t);
+ }
+
+ void setWantsMultiline(bool t)
+ {
+ recorder()->showMultiline(t);
+ }
+
+ std::string message(int n)
+ {
+ return recorder()->message(n);
+ }
+
+ void reportMessages()
+ {
+ recorder()->reportMessages();
+ }
+
+ void ensure_message_count(int expectedCount)
+ {
+ ensure_equals("message count", countMessages(), expectedCount);
+ }
+
+ std::string message_field(int msgnum, LogFieldIndex fieldnum)
+ {
+ std::ostringstream test_name;
+ test_name << "testing message " << msgnum << ", not enough messages";
+ tut::ensure(test_name.str(), msgnum < countMessages());
+
+ std::string msg(message(msgnum));
+
+ std::string field_value;
+
+ // find the start of the field; fields are separated by a single space
+ size_t scan = 0;
+ int on_field = 0;
+ while ( scan < msg.length() && on_field < fieldnum )
+ {
+ // fields are delimited by one space
+ if ( ' ' == msg[scan] )
+ {
+ if ( on_field < FUNCTION_FIELD )
+ {
+ on_field++;
+ }
+ // except function, which may have embedded spaces so ends with " : "
+ else if (( on_field == FUNCTION_FIELD )
+ && ( ':' == msg[scan+1] && ' ' == msg[scan+2] )
+ )
+ {
+ on_field++;
+ scan +=2;
+ }
+ }
+ scan++;
+ }
+ size_t start_field = scan;
+ size_t fieldlen = 0;
+ if ( fieldnum < FUNCTION_FIELD )
+ {
+ fieldlen = msg.find(' ', start_field) - start_field;
+ }
+ else if ( fieldnum == FUNCTION_FIELD )
+ {
+ fieldlen = msg.find(" : ", start_field) - start_field;
+ }
+ else if ( MSG_FIELD == fieldnum ) // no delimiter, just everything to the end
+ {
+ fieldlen = msg.length() - start_field;
+ }
+
+ return msg.substr(start_field, fieldlen);
+ }
+
+ void ensure_message_field_equals(int msgnum, LogFieldIndex fieldnum, const std::string& expectedText)
+ {
+ std::ostringstream test_name;
+ test_name << "testing message " << msgnum << " field " << FieldName[fieldnum] << "\n message: \"" << message(msgnum).substr(0, 100) << "\"\n ";
+
+ try
+ {
+ ensure_equals(test_name.str(), message_field(msgnum, fieldnum), expectedText);
+ }
+ catch (const failure&)
+ {
+ reportMessages();
+ throw;
+ }
+ }
+
+ void ensure_message_does_not_contain(int n, const std::string& expectedText)
+ {
+ std::ostringstream test_name;
+ test_name << "testing message " << n;
+
+ try
+ {
+ ensure_does_not_contain(test_name.str(), message(n), expectedText);
+ }
+ catch (const failure&)
+ {
+ reportMessages();
+ throw;
+ }
+ }
+ };
+
+ typedef test_group<ErrorTestData> ErrorTestGroup;
+ typedef ErrorTestGroup::object ErrorTestObject;
+
+ ErrorTestGroup errorTestGroup("error");
+
+ template<> template<>
+ void ErrorTestObject::test<1>()
+ // basic test of output
+ {
+ LL_INFOS() << "test" << LL_ENDL;
+ LL_INFOS() << "bob" << LL_ENDL;
+
+ ensure_message_field_equals(0, MSG_FIELD, "test");
+ ensure_message_field_equals(1, MSG_FIELD, "bob");
+ }
}
namespace
{
- void writeSome()
- {
- LL_DEBUGS("WriteTag","AnotherTag") << "one" << LL_ENDL;
- LL_INFOS("WriteTag") << "two" << LL_ENDL;
- LL_WARNS("WriteTag") << "three" << LL_ENDL;
- CATCH(LL_ERRS("WriteTag"), "four");
- }
+ void writeSome()
+ {
+ LL_DEBUGS("WriteTag","AnotherTag") << "one" << LL_ENDL;
+ LL_INFOS("WriteTag") << "two" << LL_ENDL;
+ LL_WARNS("WriteTag") << "three" << LL_ENDL;
+ CATCH(LL_ERRS("WriteTag"), "four");
+ }
};
namespace tut
{
- template<> template<>
- void ErrorTestObject::test<2>()
- // messages are filtered based on default level
- {
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- writeSome();
- ensure_message_field_equals(0, MSG_FIELD, "one");
- ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG");
- ensure_message_field_equals(0, TAGS_FIELD, "#WriteTag#AnotherTag#");
- ensure_message_field_equals(1, MSG_FIELD, "two");
- ensure_message_field_equals(1, LEVEL_FIELD, "INFO");
- ensure_message_field_equals(1, TAGS_FIELD, "#WriteTag#");
- ensure_message_field_equals(2, MSG_FIELD, "three");
- ensure_message_field_equals(2, LEVEL_FIELD, "WARNING");
- ensure_message_field_equals(2, TAGS_FIELD, "#WriteTag#");
- ensure_message_field_equals(3, MSG_FIELD, "four");
- ensure_message_field_equals(3, LEVEL_FIELD, "ERROR");
- ensure_message_field_equals(3, TAGS_FIELD, "#WriteTag#");
- ensure_message_count(4);
-
- LLError::setDefaultLevel(LLError::LEVEL_INFO);
- writeSome();
- ensure_message_field_equals(4, MSG_FIELD, "two");
- ensure_message_field_equals(5, MSG_FIELD, "three");
- ensure_message_field_equals(6, MSG_FIELD, "four");
- ensure_message_count(7);
-
- LLError::setDefaultLevel(LLError::LEVEL_WARN);
- writeSome();
- ensure_message_field_equals(7, MSG_FIELD, "three");
- ensure_message_field_equals(8, MSG_FIELD, "four");
- ensure_message_count(9);
-
- LLError::setDefaultLevel(LLError::LEVEL_ERROR);
- writeSome();
- ensure_message_field_equals(9, MSG_FIELD, "four");
- ensure_message_count(10);
-
- LLError::setDefaultLevel(LLError::LEVEL_NONE);
- writeSome();
- ensure_message_count(10);
- }
-
- template<> template<>
- void ErrorTestObject::test<3>()
- // error type string in output
- {
- writeSome();
- ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG");
- ensure_message_field_equals(1, LEVEL_FIELD, "INFO");
- ensure_message_field_equals(2, LEVEL_FIELD, "WARNING");
- ensure_message_field_equals(3, LEVEL_FIELD, "ERROR");
- ensure_message_count(4);
- }
-
- template<> template<>
- void ErrorTestObject::test<4>()
- // file abbreviation
- {
- 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,
- "llcommon/tests/llerror_test.cpp"
- );
- ensure_does_not_contain("file name abbreviation",
- abbreviateFile, "indra");
-
- std::string someFile =
+ template<> template<>
+ void ErrorTestObject::test<2>()
+ // messages are filtered based on default level
+ {
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ writeSome();
+ ensure_message_field_equals(0, MSG_FIELD, "one");
+ ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG");
+ ensure_message_field_equals(0, TAGS_FIELD, "#WriteTag#AnotherTag#");
+ ensure_message_field_equals(1, MSG_FIELD, "two");
+ ensure_message_field_equals(1, LEVEL_FIELD, "INFO");
+ ensure_message_field_equals(1, TAGS_FIELD, "#WriteTag#");
+ ensure_message_field_equals(2, MSG_FIELD, "three");
+ ensure_message_field_equals(2, LEVEL_FIELD, "WARNING");
+ ensure_message_field_equals(2, TAGS_FIELD, "#WriteTag#");
+ ensure_message_field_equals(3, MSG_FIELD, "four");
+ ensure_message_field_equals(3, LEVEL_FIELD, "ERROR");
+ ensure_message_field_equals(3, TAGS_FIELD, "#WriteTag#");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(5);
+
+ LLError::setDefaultLevel(LLError::LEVEL_INFO);
+ writeSome();
+ ensure_message_field_equals(5, MSG_FIELD, "two");
+ ensure_message_field_equals(6, MSG_FIELD, "three");
+ ensure_message_field_equals(7, MSG_FIELD, "four");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(9);
+
+ LLError::setDefaultLevel(LLError::LEVEL_WARN);
+ writeSome();
+ ensure_message_field_equals(9, MSG_FIELD, "three");
+ ensure_message_field_equals(10, MSG_FIELD, "four");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(12);
+
+ LLError::setDefaultLevel(LLError::LEVEL_ERROR);
+ writeSome();
+ ensure_message_field_equals(12, MSG_FIELD, "four");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(14);
+
+ LLError::setDefaultLevel(LLError::LEVEL_NONE);
+ writeSome();
+ ensure_message_count(14);
+ }
+
+ template<> template<>
+ void ErrorTestObject::test<3>()
+ // error type string in output
+ {
+ writeSome();
+ ensure_message_field_equals(0, LEVEL_FIELD, "DEBUG");
+ ensure_message_field_equals(1, LEVEL_FIELD, "INFO");
+ ensure_message_field_equals(2, LEVEL_FIELD, "WARNING");
+ ensure_message_field_equals(3, LEVEL_FIELD, "ERROR");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(5);
+ }
+
+ template<> template<>
+ void ErrorTestObject::test<4>()
+ // file abbreviation
+ {
+ 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,
+ "llcommon/tests/llerror_test.cpp"
+ );
+ ensure_does_not_contain("file name abbreviation",
+ abbreviateFile, "indra");
+
+ std::string someFile =
#if LL_WINDOWS
- "C:/amy/bob/cam.cpp"
+ "C:/amy/bob/cam.cpp"
#else
- "/amy/bob/cam.cpp"
+ "/amy/bob/cam.cpp"
#endif
- ;
- std::string someAbbreviation = LLError::abbreviateFile(someFile);
+ ;
+ std::string someAbbreviation = LLError::abbreviateFile(someFile);
- ensure_equals("non-indra file abbreviation",
- someAbbreviation, someFile);
- }
+ ensure_equals("non-indra file abbreviation",
+ someAbbreviation, someFile);
+ }
}
namespace
{
- std::string locationString(int line)
- {
- std::ostringstream location;
- location << LLError::abbreviateFile(__FILE__)
- << "(" << line << ")";
-
- return location.str();
- }
-
- std::string writeReturningLocation()
- {
- LL_INFOS() << "apple" << LL_ENDL; int this_line = __LINE__;
- return locationString(this_line);
- }
-
- void writeReturningLocationAndFunction(std::string& location, std::string& function)
- {
- LL_INFOS() << "apple" << LL_ENDL; int this_line = __LINE__;
- location = locationString(this_line);
- function = __FUNCTION__;
- }
-
- std::string errorReturningLocation()
- {
- int this_line = __LINE__; CATCH(LL_ERRS(), "die");
- return locationString(this_line);
- }
+ std::string locationString(int line)
+ {
+ std::ostringstream location;
+ location << LLError::abbreviateFile(__FILE__)
+ << "(" << line << ")";
+
+ return location.str();
+ }
+
+ std::string writeReturningLocation()
+ {
+ LL_INFOS() << "apple" << LL_ENDL; int this_line = __LINE__;
+ return locationString(this_line);
+ }
+
+ void writeReturningLocationAndFunction(std::string& location, std::string& function)
+ {
+ LL_INFOS() << "apple" << LL_ENDL; int this_line = __LINE__;
+ location = locationString(this_line);
+ function = __FUNCTION__;
+ }
+
+ std::string errorReturningLocation()
+ {
+ int this_line = __LINE__; CATCH(LL_ERRS(), "die");
+ return locationString(this_line);
+ }
}
/* The following helper functions and class members all log a simple message
- from some particular function scope. Each function takes a bool argument
- that indicates if it should log its own name or not (in the manner that
- existing log messages often do.) The functions all return their C++
- name so that test can be substantial mechanized.
+ from some particular function scope. Each function takes a bool argument
+ that indicates if it should log its own name or not (in the manner that
+ existing log messages often do.) The functions all return their C++
+ name so that test can be substantial mechanized.
*/
std::string logFromGlobal(bool id)
{
- LL_INFOS() << (id ? "logFromGlobal: " : "") << "hi" << LL_ENDL;
- return "logFromGlobal";
+ LL_INFOS() << (id ? "logFromGlobal: " : "") << "hi" << LL_ENDL;
+ return "logFromGlobal";
}
static std::string logFromStatic(bool id)
{
- LL_INFOS() << (id ? "logFromStatic: " : "") << "hi" << LL_ENDL;
- return "logFromStatic";
+ LL_INFOS() << (id ? "logFromStatic: " : "") << "hi" << LL_ENDL;
+ return "logFromStatic";
}
namespace
{
- std::string logFromAnon(bool id)
- {
- LL_INFOS() << (id ? "logFromAnon: " : "") << "hi" << LL_ENDL;
- return "logFromAnon";
- }
+ std::string logFromAnon(bool id)
+ {
+ LL_INFOS() << (id ? "logFromAnon: " : "") << "hi" << LL_ENDL;
+ return "logFromAnon";
+ }
}
namespace Foo {
- std::string logFromNamespace(bool id)
- {
- LL_INFOS() << (id ? "Foo::logFromNamespace: " : "") << "hi" << LL_ENDL;
- //return "Foo::logFromNamespace";
- // there is no standard way to get the namespace name, hence
- // we won't be testing for it
- return "logFromNamespace";
- }
+ std::string logFromNamespace(bool id)
+ {
+ LL_INFOS() << (id ? "Foo::logFromNamespace: " : "") << "hi" << LL_ENDL;
+ //return "Foo::logFromNamespace";
+ // there is no standard way to get the namespace name, hence
+ // we won't be testing for it
+ return "logFromNamespace";
+ }
}
namespace
{
- class ClassWithNoLogType {
- public:
- std::string logFromMember(bool id)
- {
- LL_INFOS() << (id ? "ClassWithNoLogType::logFromMember: " : "") << "hi" << LL_ENDL;
- return "ClassWithNoLogType::logFromMember";
- }
- static std::string logFromStatic(bool id)
- {
- LL_INFOS() << (id ? "ClassWithNoLogType::logFromStatic: " : "") << "hi" << LL_ENDL;
- return "ClassWithNoLogType::logFromStatic";
- }
- };
-
- class ClassWithLogType {
- LOG_CLASS(ClassWithLogType);
- public:
- std::string logFromMember(bool id)
- {
- LL_INFOS() << (id ? "ClassWithLogType::logFromMember: " : "") << "hi" << LL_ENDL;
- return "ClassWithLogType::logFromMember";
- }
- static std::string logFromStatic(bool id)
- {
- LL_INFOS() << (id ? "ClassWithLogType::logFromStatic: " : "") << "hi" << LL_ENDL;
- return "ClassWithLogType::logFromStatic";
- }
- };
-
- std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); }
- std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); }
- std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); }
-
- void ensure_has(const std::string& message,
- const std::string& actual, const std::string& expected)
- {
- std::string::size_type n1 = actual.find(expected);
- if (n1 == std::string::npos)
- {
- std::stringstream ss;
- ss << message << ": " << "expected to find a copy of '" << expected
- << "' in actual '" << actual << "'";
- throw tut::failure(ss.str().c_str());
- }
- }
-
- typedef std::string (*LogFromFunction)(bool);
- void testLogName(LLError::RecorderPtr recorder, LogFromFunction f,
- const std::string& class_name = "")
- {
- std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->clearMessages();
- std::string name = f(false);
- f(true);
-
- std::string messageWithoutName = std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(0);
- std::string messageWithName = std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(1);
-
- ensure_has(name + " logged without name",
- messageWithoutName, name);
- ensure_has(name + " logged with name",
- messageWithName, name);
-
- if (!class_name.empty())
- {
- ensure_has(name + "logged without name",
- messageWithoutName, class_name);
- ensure_has(name + "logged with name",
- messageWithName, class_name);
- }
- }
+ class ClassWithNoLogType {
+ public:
+ std::string logFromMember(bool id)
+ {
+ LL_INFOS() << (id ? "ClassWithNoLogType::logFromMember: " : "") << "hi" << LL_ENDL;
+ return "ClassWithNoLogType::logFromMember";
+ }
+ static std::string logFromStatic(bool id)
+ {
+ LL_INFOS() << (id ? "ClassWithNoLogType::logFromStatic: " : "") << "hi" << LL_ENDL;
+ return "ClassWithNoLogType::logFromStatic";
+ }
+ };
+
+ class ClassWithLogType {
+ LOG_CLASS(ClassWithLogType);
+ public:
+ std::string logFromMember(bool id)
+ {
+ LL_INFOS() << (id ? "ClassWithLogType::logFromMember: " : "") << "hi" << LL_ENDL;
+ return "ClassWithLogType::logFromMember";
+ }
+ static std::string logFromStatic(bool id)
+ {
+ LL_INFOS() << (id ? "ClassWithLogType::logFromStatic: " : "") << "hi" << LL_ENDL;
+ return "ClassWithLogType::logFromStatic";
+ }
+ };
+
+ std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); }
+ std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); }
+ std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); }
+
+ void ensure_has(const std::string& message,
+ const std::string& actual, const std::string& expected)
+ {
+ std::string::size_type n1 = actual.find(expected);
+ if (n1 == std::string::npos)
+ {
+ std::stringstream ss;
+ ss << message << ": " << "expected to find a copy of '" << expected
+ << "' in actual '" << actual << "'";
+ throw tut::failure(ss.str().c_str());
+ }
+ }
+
+ typedef std::string (*LogFromFunction)(bool);
+ void testLogName(LLError::RecorderPtr recorder, LogFromFunction f,
+ const std::string& class_name = "")
+ {
+ std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->clearMessages();
+ std::string name = f(false);
+ f(true);
+
+ std::string messageWithoutName = std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(0);
+ std::string messageWithName = std::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(1);
+
+ ensure_has(name + " logged without name",
+ messageWithoutName, name);
+ ensure_has(name + " logged with name",
+ messageWithName, name);
+
+ if (!class_name.empty())
+ {
+ ensure_has(name + "logged without name",
+ messageWithoutName, class_name);
+ ensure_has(name + "logged with name",
+ messageWithName, class_name);
+ }
+ }
}
namespace
@@ -540,7 +578,7 @@ namespace tut
// backslash, return, and newline are not escaped with backslashes
{
LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- setWantsMultiline(true);
+ setWantsMultiline(true);
writeMsgNeedsEscaping(); // but should not be now
ensure_message_field_equals(0, MSG_FIELD, "backslash\\");
ensure_message_field_equals(1, MSG_FIELD, "newline\nafternewline");
@@ -554,324 +592,329 @@ namespace tut
namespace tut
{
- template<> template<>
- // class/function information in output
- void ErrorTestObject::test<6>()
- {
- testLogName(mRecorder, logFromGlobal);
- testLogName(mRecorder, logFromStatic);
- testLogName(mRecorder, logFromAnon);
- testLogName(mRecorder, logFromNamespace);
- testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType");
- testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType");
- }
+ template<> template<>
+ // class/function information in output
+ void ErrorTestObject::test<6>()
+ {
+ testLogName(mRecorder, logFromGlobal);
+ testLogName(mRecorder, logFromStatic);
+ testLogName(mRecorder, logFromAnon);
+ testLogName(mRecorder, logFromNamespace);
+ testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType");
+ testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType");
+ }
}
namespace
{
- std::string innerLogger()
- {
- LL_INFOS() << "inside" << LL_ENDL;
- return "moo";
- }
-
- std::string outerLogger()
- {
- LL_INFOS() << "outside(" << innerLogger() << ")" << LL_ENDL;
- return "bar";
- }
-
- class LogWhileLogging
- {
- public:
- void print(std::ostream& out) const
- {
- LL_INFOS() << "logging" << LL_ENDL;
- out << "baz";
- }
- };
-
- std::ostream& operator<<(std::ostream& out, const LogWhileLogging& l)
- { l.print(out); return out; }
-
- void metaLogger()
- {
- LogWhileLogging l;
- LL_INFOS() << "meta(" << l << ")" << LL_ENDL;
- }
+ std::string innerLogger()
+ {
+ LL_INFOS() << "inside" << LL_ENDL;
+ return "moo";
+ }
+
+ std::string outerLogger()
+ {
+ LL_INFOS() << "outside(" << innerLogger() << ")" << LL_ENDL;
+ return "bar";
+ }
+
+ class LogWhileLogging
+ {
+ public:
+ void print(std::ostream& out) const
+ {
+ LL_INFOS() << "logging" << LL_ENDL;
+ out << "baz";
+ }
+ };
+
+ std::ostream& operator<<(std::ostream& out, const LogWhileLogging& l)
+ { l.print(out); return out; }
+
+ void metaLogger()
+ {
+ LogWhileLogging l;
+ LL_INFOS() << "meta(" << l << ")" << LL_ENDL;
+ }
}
namespace tut
{
- template<> template<>
- // handle nested logging
- void ErrorTestObject::test<7>()
- {
- outerLogger();
- ensure_message_field_equals(0, MSG_FIELD, "inside");
- ensure_message_field_equals(1, MSG_FIELD, "outside(moo)");
- ensure_message_count(2);
-
- metaLogger();
- ensure_message_field_equals(2, MSG_FIELD, "logging");
- ensure_message_field_equals(3, MSG_FIELD, "meta(baz)");
- ensure_message_count(4);
- }
-
- template<> template<>
- // special handling of LL_ERRS() calls
- void ErrorTestObject::test<8>()
- {
- std::string location = errorReturningLocation();
-
- ensure_message_field_equals(0, LOCATION_FIELD, location);
- ensure_message_field_equals(0, MSG_FIELD, "die");
- ensure_message_count(1);
-
- ensure("fatal callback called", fatalWasCalled);
- }
+ template<> template<>
+ // handle nested logging
+ void ErrorTestObject::test<7>()
+ {
+ outerLogger();
+ ensure_message_field_equals(0, MSG_FIELD, "inside");
+ ensure_message_field_equals(1, MSG_FIELD, "outside(moo)");
+ ensure_message_count(2);
+
+ metaLogger();
+ ensure_message_field_equals(2, MSG_FIELD, "logging");
+ ensure_message_field_equals(3, MSG_FIELD, "meta(baz)");
+ ensure_message_count(4);
+ }
+
+ template<> template<>
+ // special handling of LL_ERRS() calls
+ void ErrorTestObject::test<8>()
+ {
+ std::string location = errorReturningLocation();
+
+ ensure_message_field_equals(0, LOCATION_FIELD, location);
+ ensure_message_field_equals(0, MSG_FIELD, "die");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(2);
+
+ ensure("fatal callback called", fatalWasCalled);
+ }
}
namespace
{
- std::string roswell()
- {
- return "1947-07-08T03:04:05Z";
- }
-
- void ufoSighting()
- {
- LL_INFOS() << "ufo" << LL_ENDL;
- }
+ std::string roswell()
+ {
+ return "1947-07-08T03:04:05Z";
+ }
+
+ void ufoSighting()
+ {
+ LL_INFOS() << "ufo" << LL_ENDL;
+ }
}
namespace tut
{
- template<> template<>
- // time in output (for recorders that need it)
- void ErrorTestObject::test<9>()
- {
- LLError::setTimeFunction(roswell);
-
- setWantsTime(false);
- ufoSighting();
- ensure_message_field_equals(0, MSG_FIELD, "ufo");
- ensure_message_does_not_contain(0, roswell());
-
- setWantsTime(true);
- ufoSighting();
- ensure_message_field_equals(1, MSG_FIELD, "ufo");
- ensure_message_field_equals(1, TIME_FIELD, roswell());
- }
-
- template<> template<>
- // output order
- void ErrorTestObject::test<10>()
- {
- LLError::setTimeFunction(roswell);
- setWantsTime(true);
-
- std::string location,
- function;
- writeReturningLocationAndFunction(location, function);
-
- ensure_equals("order is time level tags location function message",
+ template<> template<>
+ // time in output (for recorders that need it)
+ void ErrorTestObject::test<9>()
+ {
+ LLError::setTimeFunction(roswell);
+
+ setWantsTime(false);
+ ufoSighting();
+ ensure_message_field_equals(0, MSG_FIELD, "ufo");
+ ensure_message_does_not_contain(0, roswell());
+
+ setWantsTime(true);
+ ufoSighting();
+ ensure_message_field_equals(1, MSG_FIELD, "ufo");
+ ensure_message_field_equals(1, TIME_FIELD, roswell());
+ }
+
+ template<> template<>
+ // output order
+ void ErrorTestObject::test<10>()
+ {
+ LLError::setTimeFunction(roswell);
+ setWantsTime(true);
+
+ std::string location,
+ function;
+ writeReturningLocationAndFunction(location, function);
+
+ ensure_equals("order is time level tags location function message",
message(0),
roswell() + " INFO " + "# " /* no tag */ + location + " " + function + " : " + "apple");
- }
+ }
- template<> template<>
- // multiple recorders
- void ErrorTestObject::test<11>()
- {
- LLError::RecorderPtr altRecorder(new TestRecorder());
- LLError::addRecorder(altRecorder);
+ template<> template<>
+ // multiple recorders
+ void ErrorTestObject::test<11>()
+ {
+ LLError::RecorderPtr altRecorder(new TestRecorder());
+ LLError::addRecorder(altRecorder);
- LL_INFOS() << "boo" << LL_ENDL;
+ LL_INFOS() << "boo" << LL_ENDL;
- ensure_message_field_equals(0, MSG_FIELD, "boo");
- ensure_equals("alt recorder count", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 1);
- ensure_contains("alt recorder message 0", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(0), "boo");
+ ensure_message_field_equals(0, MSG_FIELD, "boo");
+ ensure_equals("alt recorder count", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 1);
+ ensure_contains("alt recorder message 0", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(0), "boo");
- LLError::setTimeFunction(roswell);
+ LLError::setTimeFunction(roswell);
- LLError::RecorderPtr anotherRecorder(new TestRecorder());
- std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->showTime(true);
- LLError::addRecorder(anotherRecorder);
+ LLError::RecorderPtr anotherRecorder(new TestRecorder());
+ std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->showTime(true);
+ LLError::addRecorder(anotherRecorder);
- LL_INFOS() << "baz" << LL_ENDL;
+ LL_INFOS() << "baz" << LL_ENDL;
- std::string when = roswell();
+ std::string when = roswell();
- ensure_message_does_not_contain(1, when);
- ensure_equals("alt recorder count", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 2);
- ensure_does_not_contain("alt recorder message 1", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(1), when);
- ensure_equals("another recorder count", std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->countMessages(), 1);
- ensure_contains("another recorder message 0", std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->message(0), when);
+ ensure_message_does_not_contain(1, when);
+ ensure_equals("alt recorder count", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 2);
+ ensure_does_not_contain("alt recorder message 1", std::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(1), when);
+ ensure_equals("another recorder count", std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->countMessages(), 1);
+ ensure_contains("another recorder message 0", std::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->message(0), when);
- LLError::removeRecorder(altRecorder);
- LLError::removeRecorder(anotherRecorder);
- }
+ LLError::removeRecorder(altRecorder);
+ LLError::removeRecorder(anotherRecorder);
+ }
}
class TestAlpha
{
- LOG_CLASS(TestAlpha);
+ LOG_CLASS(TestAlpha);
public:
- static void doDebug() { LL_DEBUGS() << "add dice" << LL_ENDL; }
- static void doInfo() { LL_INFOS() << "any idea" << LL_ENDL; }
- static void doWarn() { LL_WARNS() << "aim west" << LL_ENDL; }
- static void doError() { CATCH(LL_ERRS(), "ate eels"); }
- static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
+ static void doDebug() { LL_DEBUGS() << "add dice" << LL_ENDL; }
+ static void doInfo() { LL_INFOS() << "any idea" << LL_ENDL; }
+ static void doWarn() { LL_WARNS() << "aim west" << LL_ENDL; }
+ static void doError() { CATCH(LL_ERRS(), "ate eels"); }
+ static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
};
class TestBeta
{
- LOG_CLASS(TestBeta);
+ LOG_CLASS(TestBeta);
public:
- static void doDebug() { LL_DEBUGS() << "bed down" << LL_ENDL; }
- static void doInfo() { LL_INFOS() << "buy iron" << LL_ENDL; }
- static void doWarn() { LL_WARNS() << "bad word" << LL_ENDL; }
- static void doError() { CATCH(LL_ERRS(), "big easy"); }
- static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
+ static void doDebug() { LL_DEBUGS() << "bed down" << LL_ENDL; }
+ static void doInfo() { LL_INFOS() << "buy iron" << LL_ENDL; }
+ static void doWarn() { LL_WARNS() << "bad word" << LL_ENDL; }
+ static void doError() { CATCH(LL_ERRS(), "big easy"); }
+ static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
};
namespace tut
{
- template<> template<>
- // filtering by class
- void ErrorTestObject::test<12>()
- {
- LLError::setDefaultLevel(LLError::LEVEL_WARN);
- LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO);
-
- TestAlpha::doAll();
- TestBeta::doAll();
-
- ensure_message_field_equals(0, MSG_FIELD, "aim west");
- ensure_message_field_equals(1, MSG_FIELD, "ate eels");
- ensure_message_field_equals(2, MSG_FIELD, "buy iron");
- ensure_message_field_equals(3, MSG_FIELD, "bad word");
- ensure_message_field_equals(4, MSG_FIELD, "big easy");
- ensure_message_count(5);
- }
-
- template<> template<>
- // filtering by function, and that it will override class filtering
- void ErrorTestObject::test<13>()
- {
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN);
- LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG);
- LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE);
-
- TestBeta::doAll();
- ensure_message_field_equals(0, MSG_FIELD, "buy iron");
- ensure_message_field_equals(1, MSG_FIELD, "bad word");
- ensure_message_count(2);
- }
-
- template<> template<>
- // filtering by file
- // and that it is overridden by both class and function filtering
- void ErrorTestObject::test<14>()
- {
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- LLError::setFileLevel(LLError::abbreviateFile(__FILE__),
- LLError::LEVEL_WARN);
- LLError::setClassLevel("TestAlpha", LLError::LEVEL_INFO);
- LLError::setFunctionLevel("TestAlpha::doError",
- LLError::LEVEL_NONE);
- LLError::setFunctionLevel("TestBeta::doError",
- LLError::LEVEL_NONE);
-
- TestAlpha::doAll();
- TestBeta::doAll();
- ensure_message_field_equals(0, MSG_FIELD, "any idea");
- ensure_message_field_equals(1, MSG_FIELD, "aim west");
- ensure_message_field_equals(2, MSG_FIELD, "bad word");
- ensure_message_count(3);
- }
-
- template<> template<>
- // proper cached, efficient lookup of filtering
- void ErrorTestObject::test<15>()
- {
- LLError::setDefaultLevel(LLError::LEVEL_NONE);
-
- TestAlpha::doInfo();
- ensure_message_count(0);
- ensure_equals("first check", LLError::shouldLogCallCount(), 1);
- TestAlpha::doInfo();
- ensure_message_count(0);
- ensure_equals("second check", LLError::shouldLogCallCount(), 1);
-
- LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG);
- TestAlpha::doInfo();
- ensure_message_count(1);
- ensure_equals("third check", LLError::shouldLogCallCount(), 2);
- TestAlpha::doInfo();
- ensure_message_count(2);
- ensure_equals("fourth check", LLError::shouldLogCallCount(), 2);
-
- LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN);
- TestAlpha::doInfo();
- ensure_message_count(2);
- ensure_equals("fifth check", LLError::shouldLogCallCount(), 3);
- TestAlpha::doInfo();
- ensure_message_count(2);
- ensure_equals("sixth check", LLError::shouldLogCallCount(), 3);
- }
-
- template<> template<>
- // configuration from LLSD
- void ErrorTestObject::test<16>()
- {
- LLSD config;
- config["print-location"] = true;
- config["default-level"] = "DEBUG";
-
- LLSD set1;
- set1["level"] = "WARN";
+ template<> template<>
+ // filtering by class
+ void ErrorTestObject::test<12>()
+ {
+ LLError::setDefaultLevel(LLError::LEVEL_WARN);
+ LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO);
+
+ TestAlpha::doAll();
+ TestBeta::doAll();
+
+ ensure_message_field_equals(0, MSG_FIELD, "aim west");
+ ensure_message_field_equals(1, MSG_FIELD, "ate eels");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_field_equals(3, MSG_FIELD, "buy iron");
+ ensure_message_field_equals(4, MSG_FIELD, "bad word");
+ ensure_message_field_equals(5, MSG_FIELD, "big easy");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(7);
+ }
+
+ template<> template<>
+ // filtering by function, and that it will override class filtering
+ void ErrorTestObject::test<13>()
+ {
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN);
+ LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG);
+ LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE);
+
+ TestBeta::doAll();
+ ensure_message_field_equals(0, MSG_FIELD, "buy iron");
+ ensure_message_field_equals(1, MSG_FIELD, "bad word");
+ ensure_message_count(2);
+ }
+
+ template<> template<>
+ // filtering by file
+ // and that it is overridden by both class and function filtering
+ void ErrorTestObject::test<14>()
+ {
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LLError::setFileLevel(LLError::abbreviateFile(__FILE__),
+ LLError::LEVEL_WARN);
+ LLError::setClassLevel("TestAlpha", LLError::LEVEL_INFO);
+ LLError::setFunctionLevel("TestAlpha::doError",
+ LLError::LEVEL_NONE);
+ LLError::setFunctionLevel("TestBeta::doError",
+ LLError::LEVEL_NONE);
+
+ TestAlpha::doAll();
+ TestBeta::doAll();
+ ensure_message_field_equals(0, MSG_FIELD, "any idea");
+ ensure_message_field_equals(1, MSG_FIELD, "aim west");
+ ensure_message_field_equals(2, MSG_FIELD, "bad word");
+ ensure_message_count(3);
+ }
+
+ template<> template<>
+ // proper cached, efficient lookup of filtering
+ void ErrorTestObject::test<15>()
+ {
+ LLError::setDefaultLevel(LLError::LEVEL_NONE);
+
+ TestAlpha::doInfo();
+ ensure_message_count(0);
+ ensure_equals("first check", LLError::shouldLogCallCount(), 1);
+ TestAlpha::doInfo();
+ ensure_message_count(0);
+ ensure_equals("second check", LLError::shouldLogCallCount(), 1);
+
+ LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG);
+ TestAlpha::doInfo();
+ ensure_message_count(1);
+ ensure_equals("third check", LLError::shouldLogCallCount(), 2);
+ TestAlpha::doInfo();
+ ensure_message_count(2);
+ ensure_equals("fourth check", LLError::shouldLogCallCount(), 2);
+
+ LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN);
+ TestAlpha::doInfo();
+ ensure_message_count(2);
+ ensure_equals("fifth check", LLError::shouldLogCallCount(), 3);
+ TestAlpha::doInfo();
+ ensure_message_count(2);
+ ensure_equals("sixth check", LLError::shouldLogCallCount(), 3);
+ }
+
+ template<> template<>
+ // configuration from LLSD
+ void ErrorTestObject::test<16>()
+ {
+ LLSD config;
+ config["print-location"] = true;
+ config["default-level"] = "DEBUG";
+
+ LLSD set1;
+ set1["level"] = "WARN";
set1["files"][0] = LLError::abbreviateFile(__FILE__);
- LLSD set2;
- set2["level"] = "INFO";
- set2["classes"][0] = "TestAlpha";
-
- LLSD set3;
- set3["level"] = "NONE";
- set3["functions"][0] = "TestAlpha::doError";
- set3["functions"][1] = "TestBeta::doError";
-
- config["settings"][0] = set1;
- config["settings"][1] = set2;
- config["settings"][2] = set3;
-
- LLError::configure(config);
-
- TestAlpha::doAll();
- TestBeta::doAll();
- ensure_message_field_equals(0, MSG_FIELD, "any idea");
- ensure_message_field_equals(1, MSG_FIELD, "aim west");
- ensure_message_field_equals(2, MSG_FIELD, "bad word");
- ensure_message_count(3);
-
- // make sure reconfiguring works
- LLSD config2;
- config2["default-level"] = "WARN";
-
- LLError::configure(config2);
-
- TestAlpha::doAll();
- TestBeta::doAll();
- ensure_message_field_equals(3, MSG_FIELD, "aim west");
- ensure_message_field_equals(4, MSG_FIELD, "ate eels");
- ensure_message_field_equals(5, MSG_FIELD, "bad word");
- ensure_message_field_equals(6, MSG_FIELD, "big easy");
- ensure_message_count(7);
- }
+ LLSD set2;
+ set2["level"] = "INFO";
+ set2["classes"][0] = "TestAlpha";
+
+ LLSD set3;
+ set3["level"] = "NONE";
+ set3["functions"][0] = "TestAlpha::doError";
+ set3["functions"][1] = "TestBeta::doError";
+
+ config["settings"][0] = set1;
+ config["settings"][1] = set2;
+ config["settings"][2] = set3;
+
+ LLError::configure(config);
+
+ TestAlpha::doAll();
+ TestBeta::doAll();
+ ensure_message_field_equals(0, MSG_FIELD, "any idea");
+ ensure_message_field_equals(1, MSG_FIELD, "aim west");
+ ensure_message_field_equals(2, MSG_FIELD, "bad word");
+ ensure_message_count(3);
+
+ // make sure reconfiguring works
+ LLSD config2;
+ config2["default-level"] = "WARN";
+
+ LLError::configure(config2);
+
+ TestAlpha::doAll();
+ TestBeta::doAll();
+ ensure_message_field_equals(3, MSG_FIELD, "aim west");
+ ensure_message_field_equals(4, MSG_FIELD, "ate eels");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_field_equals(6, MSG_FIELD, "bad word");
+ ensure_message_field_equals(7, MSG_FIELD, "big easy");
+ // LL_ERRS() produces 2 recordMessage() calls
+ ensure_message_count(9);
+ }
}
namespace tut
@@ -919,16 +962,16 @@ namespace tut
}
/* Tests left:
- handling of classes without LOG_CLASS
+ handling of classes without LOG_CLASS
- live update of filtering from file
+ live update of filtering from file
- syslog recorder
- file recorder
- cerr/stderr recorder
- fixed buffer recorder
- windows recorder
+ syslog recorder
+ file recorder
+ cerr/stderr recorder
+ fixed buffer recorder
+ windows recorder
- mutex use when logging (?)
- strange careful about to crash handling (?)
+ mutex use when logging (?)
+ strange careful about to crash handling (?)
*/
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index a3c54ffaa2..e7674fde37 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-04-22
* @brief Test for coroutine.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -113,14 +113,13 @@ namespace tut
void test_data::explicit_wait(std::shared_ptr<LLCoros::Promise<std::string>>& cbp)
{
- BEGIN
- {
- mSync.bump();
- // The point of this test is to verify / illustrate suspending a
- // coroutine for something other than an LLEventPump. In other
- // words, this shows how to adapt to any async operation that
- // provides a callback-style notification (and prove that it
- // works).
+ DEBUG;
+ mSync.bump();
+ // The point of this test is to verify / illustrate suspending a
+ // coroutine for something other than an LLEventPump. In other
+ // words, this shows how to adapt to any async operation that
+ // provides a callback-style notification (and prove that it
+ // works).
// Perhaps we would send a request to a remote server and arrange
// for cbp->set_value() to be called on response.
@@ -130,13 +129,11 @@ namespace tut
cbp = std::make_shared<LLCoros::Promise<std::string>>();
LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp);
- // calling get() on the future causes us to suspend
- debug("about to suspend");
- stringdata = future.get();
- mSync.bump();
- ensure_equals("Got it", stringdata, "received");
- }
- END
+ // calling get() on the future causes us to suspend
+ debug("about to suspend");
+ stringdata = future.get();
+ mSync.bump();
+ ensure_equals("Got it", stringdata, "received");
}
template<> template<>
@@ -163,13 +160,9 @@ namespace tut
void test_data::waitForEventOn1()
{
- BEGIN
- {
- mSync.bump();
- result = suspendUntilEventOn("source");
- mSync.bump();
- }
- END
+ mSync.bump();
+ result = suspendUntilEventOn("source");
+ mSync.bump();
}
template<> template<>
@@ -189,15 +182,11 @@ namespace tut
void test_data::coroPump()
{
- BEGIN
- {
- mSync.bump();
- LLCoroEventPump waiter;
- replyName = waiter.getName();
- result = waiter.suspend();
- mSync.bump();
- }
- END
+ mSync.bump();
+ LLCoroEventPump waiter;
+ replyName = waiter.getName();
+ result = waiter.suspend();
+ mSync.bump();
}
template<> template<>
@@ -217,16 +206,12 @@ namespace tut
void test_data::postAndWait1()
{
- BEGIN
- {
- mSync.bump();
- result = postAndSuspend(LLSDMap("value", 17), // request event
- immediateAPI.getPump(), // requestPump
- "reply1", // replyPump
- "reply"); // request["reply"] = name
- mSync.bump();
- }
- END
+ mSync.bump();
+ result = postAndSuspend(LLSDMap("value", 17), // request event
+ immediateAPI.getPump(), // requestPump
+ "reply1", // replyPump
+ "reply"); // request["reply"] = name
+ mSync.bump();
}
template<> template<>
@@ -240,15 +225,11 @@ namespace tut
void test_data::coroPumpPost()
{
- BEGIN
- {
- mSync.bump();
- LLCoroEventPump waiter;
- result = waiter.postAndSuspend(LLSDMap("value", 17),
- immediateAPI.getPump(), "reply");
- mSync.bump();
- }
- END
+ mSync.bump();
+ LLCoroEventPump waiter;
+ result = waiter.postAndSuspend(LLSDMap("value", 17),
+ immediateAPI.getPump(), "reply");
+ mSync.bump();
}
template<> template<>
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index a99acba848..244cd07ac9 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2011-01-20
* @brief Test for lleventdispatcher.
- *
+ *
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
* Copyright (c) 2011, Linden Research, Inc.
* $/LicenseInfo$
@@ -17,13 +17,13 @@
// std headers
// external library headers
// other Linden headers
+#include "StringVec.h"
#include "../test/lltut.h"
#include "lleventfilter.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "stringize.h"
-#include "StringVec.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
#include "../test/debug.h"
@@ -470,7 +470,7 @@ namespace tut
params["a"], "\n"
"params[\"b\"]:\n",
params["b"]);
- // default LLSD::Binary value
+ // default LLSD::Binary value
std::vector<U8> binary;
for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11)
{
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index a01d7fe415..a3d55d0cc6 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -3,25 +3,25 @@
* @author Nat Goodspeed
* @date 2009-03-06
* @brief Test for lleventfilter.
- *
+ *
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -34,10 +34,10 @@
// std headers
// external library headers
// other Linden headers
+#include "listener.h"
#include "../test/lltut.h"
#include "stringize.h"
#include "llsdutil.h"
-#include "listener.h"
#include "tests/wrapllerrs.h"
#include <typeinfo>
@@ -51,6 +51,7 @@
// as we've carefully put all functionality except actual LLTimer calls into
// LLEventTimeoutBase, that should suffice. We're not not not trying to test
// LLTimer here.
+#if 0 // time testing needs reworking
class TestEventTimeout: public LLEventTimeoutBase
{
public:
@@ -151,6 +152,7 @@ public:
F32 mAlarmRemaining, mTimerRemaining;
LLEventTimeoutBase::Action mAlarmAction;
};
+#endif // time testing needs reworking
/*****************************************************************************
* TUT
@@ -220,6 +222,8 @@ namespace tut
void filter_object::test<2>()
{
set_test_name("LLEventTimeout::actionAfter()");
+ skip("time testing needs reworking");
+#if 0 // time testing needs reworking
LLEventPump& driver(pumps.obtain("driver"));
TestEventTimeout filter(driver);
listener0.reset(0);
@@ -285,12 +289,15 @@ namespace tut
filter.forceTimeout();
mainloop.post(17);
check_listener("no timeout 6", listener1, LLSD(0));
+#endif // time testing needs reworking
}
template<> template<>
void filter_object::test<3>()
{
set_test_name("LLEventTimeout::eventAfter()");
+ skip("time testing needs reworking");
+#if 0 // time testing needs reworking
LLEventPump& driver(pumps.obtain("driver"));
TestEventTimeout filter(driver);
listener0.reset(0);
@@ -322,12 +329,15 @@ namespace tut
filter.forceTimeout();
mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0));
+#endif // time testing needs reworking
}
template<> template<>
void filter_object::test<4>()
{
set_test_name("LLEventTimeout::errorAfter()");
+ skip("time testing needs reworking");
+#if 0 // time testing needs reworking
WrapLLErrs capture;
LLEventPump& driver(pumps.obtain("driver"));
TestEventTimeout filter(driver);
@@ -362,12 +372,15 @@ namespace tut
filter.forceTimeout();
mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0));
+#endif // time testing needs reworking
}
template<> template<>
void filter_object::test<5>()
{
set_test_name("LLEventThrottle");
+ skip("time testing needs reworking");
+#if 0 // time testing needs reworking
TestEventThrottle throttle(3);
Concat cat;
throttle.listen("concat", boost::ref(cat));
@@ -403,6 +416,7 @@ namespace tut
throttle.advance(5);
throttle.post(";17");
ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
+#endif // time testing needs reworking
}
template<class PUMP>
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index fa48bcdefd..a7661cc7d8 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2012-02-21
* @brief Test for llleap.
- *
+ *
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Copyright (c) 2012, Linden Research, Inc.
* $/LicenseInfo$
@@ -18,6 +18,7 @@
#include <functional>
// external library headers
// other Linden headers
+#include "StringVec.h"
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
#include "../test/catch_and_store_what_in.h"
@@ -26,7 +27,6 @@
#include "llprocess.h"
#include "llstring.h"
#include "stringize.h"
-#include "StringVec.h"
#if defined(LL_WINDOWS)
#define sleep(secs) _sleep((secs) * 1000)
@@ -385,8 +385,7 @@ namespace tut
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
" else 'bad: ' + str(resp)\n"
"send(pump='" << result.getName() << "', data=result)\n";});
- waitfor(LLLeap::create(get_test_name(),
- StringVec{PYTHON, script.getName()}));
+ waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}));
result.ensure();
}
diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp
index 9ccf391327..4a15e30a30 100644
--- a/indra/llcommon/tests/llmainthreadtask_test.cpp
+++ b/indra/llcommon/tests/llmainthreadtask_test.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2019-12-05
* @brief Test for llmainthreadtask.
- *
+ *
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
* Copyright (c) 2019, Linden Research, Inc.
* $/LicenseInfo$
@@ -20,8 +20,8 @@
// other Linden headers
#include "../test/lltut.h"
#include "../test/sync.h"
+#include "llcallbacklist.h"
#include "llthread.h" // on_main_thread()
-#include "lleventtimer.h"
#include "lockstatic.h"
/*****************************************************************************
@@ -108,7 +108,7 @@ namespace tut
lk.unlock();
// run the task -- should unblock thread, which will immediately block
// on mSync
- LLEventTimer::updateClass();
+ LLCallbackList::instance().callFunctions();
// 'lk', having unlocked, can no longer be used to access; relock with
// a new LockStatic instance
ensure("should now have run", LockStatic()->ran);
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 6e8422ca0c..deef717e5c 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2011-12-19
* @brief Test for llprocess.
- *
+ *
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
* Copyright (c) 2011, Linden Research, Inc.
* $/LicenseInfo$
@@ -259,6 +259,7 @@ public:
}
std::string getName() const { return mPath.string(); }
+ std::string getNormalName() const { return mPath.lexically_normal().make_preferred().string(); }
private:
boost::filesystem::path mPath;
@@ -590,7 +591,7 @@ namespace tut
" f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n");
// Before running, call setWorkingDirectory()
py.mParams.cwd = tempdir.getName();
- std::string expected{ tempdir.getName() };
+ std::string expected{ tempdir.getNormalName() };
#if LL_WINDOWS
// SIGH, don't get tripped up by "C:" != "c:" --
// but on the Mac, using tolower() fails because "/users" != "/Users"!
@@ -1075,7 +1076,7 @@ namespace tut
{
EventListener(LLEventPump& pump)
{
- mConnection =
+ mConnection =
pump.listen("EventListener", boost::bind(&EventListener::tick, this, _1));
}
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index 56fdc51e82..bff647cbe8 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* @file llsdserialize_test.cpp
* @date 2006-04
* @brief LLSDSerialize unit tests
@@ -6,21 +6,21 @@
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
- *
+ *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
+ *
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -44,19 +44,17 @@ typedef U32 uint32_t;
#include "llstring.h"
#endif
-#include "boost/range.hpp"
-
#include "llsd.h"
#include "llsdserialize.h"
#include "llsdutil.h"
#include "llformat.h"
#include "llmemorystream.h"
-#include "../test/hexdump.h"
+#include "hexdump.h"
+#include "StringVec.h"
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
#include "stringize.h"
-#include "StringVec.h"
#include <functional>
typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction;
@@ -64,313 +62,313 @@ typedef std::function<bool(std::istream& istr, LLSD& data, llssize max_bytes)> P
std::vector<U8> string_to_vector(const std::string& str)
{
- return std::vector<U8>(str.begin(), str.end());
+ return std::vector<U8>(str.begin(), str.end());
}
namespace tut
{
- struct sd_xml_data
- {
- sd_xml_data()
- {
- mFormatter = new LLSDXMLFormatter;
- }
- LLSD mSD;
- LLPointer<LLSDXMLFormatter> mFormatter;
- void xml_test(const char* name, const std::string& expected)
- {
- std::ostringstream ostr;
- mFormatter->format(mSD, ostr);
- ensure_equals(name, ostr.str(), expected);
- }
- };
-
- typedef test_group<sd_xml_data> sd_xml_test;
- typedef sd_xml_test::object sd_xml_object;
- tut::sd_xml_test sd_xml_stream("LLSDXMLFormatter");
-
- template<> template<>
- void sd_xml_object::test<1>()
- {
- // random atomic tests
- std::string expected;
-
- expected = "<llsd><undef /></llsd>\n";
- xml_test("undef", expected);
-
- mSD = 3463;
- expected = "<llsd><integer>3463</integer></llsd>\n";
- xml_test("integer", expected);
-
- mSD = "";
- expected = "<llsd><string /></llsd>\n";
- xml_test("empty string", expected);
-
- mSD = "foobar";
- expected = "<llsd><string>foobar</string></llsd>\n";
- xml_test("string", expected);
-
- mSD = LLUUID::null;
- expected = "<llsd><uuid /></llsd>\n";
- xml_test("null uuid", expected);
-
- mSD = LLUUID("c96f9b1e-f589-4100-9774-d98643ce0bed");
- expected = "<llsd><uuid>c96f9b1e-f589-4100-9774-d98643ce0bed</uuid></llsd>\n";
- xml_test("uuid", expected);
-
- mSD = LLURI("https://secondlife.com/login");
- expected = "<llsd><uri>https://secondlife.com/login</uri></llsd>\n";
- xml_test("uri", expected);
-
- mSD = LLDate("2006-04-24T16:11:33Z");
- expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n";
- xml_test("date", expected);
-
- // Generated by: echo -n 'hello' | openssl enc -e -base64
- std::vector<U8> hello;
- hello.push_back('h');
- hello.push_back('e');
- hello.push_back('l');
- hello.push_back('l');
- hello.push_back('o');
- mSD = hello;
- expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
- xml_test("binary", expected);
- }
-
- template<> template<>
- void sd_xml_object::test<2>()
- {
- // tests with boolean values.
- std::string expected;
-
- mFormatter->boolalpha(true);
- mSD = true;
- expected = "<llsd><boolean>true</boolean></llsd>\n";
- xml_test("bool alpha true", expected);
- mSD = false;
- expected = "<llsd><boolean>false</boolean></llsd>\n";
- xml_test("bool alpha false", expected);
-
- mFormatter->boolalpha(false);
- mSD = true;
- expected = "<llsd><boolean>1</boolean></llsd>\n";
- xml_test("bool true", expected);
- mSD = false;
- expected = "<llsd><boolean>0</boolean></llsd>\n";
- xml_test("bool false", expected);
- }
-
-
- template<> template<>
- void sd_xml_object::test<3>()
- {
- // tests with real values.
- std::string expected;
-
- mFormatter->realFormat("%.2f");
- mSD = 1.0;
- expected = "<llsd><real>1.00</real></llsd>\n";
- xml_test("real 1", expected);
-
- mSD = -34379.0438;
- expected = "<llsd><real>-34379.04</real></llsd>\n";
- xml_test("real reduced precision", expected);
- mFormatter->realFormat("%.4f");
- expected = "<llsd><real>-34379.0438</real></llsd>\n";
- xml_test("higher precision", expected);
-
- mFormatter->realFormat("%.0f");
- mSD = 0.0;
- expected = "<llsd><real>0</real></llsd>\n";
- xml_test("no decimal 0", expected);
- mSD = 3287.4387;
- expected = "<llsd><real>3287</real></llsd>\n";
- xml_test("no decimal real number", expected);
- }
-
- template<> template<>
- void sd_xml_object::test<4>()
- {
- // tests with arrays
- std::string expected;
-
- mSD = LLSD::emptyArray();
- expected = "<llsd><array /></llsd>\n";
- xml_test("empty array", expected);
-
- mSD.append(LLSD());
- expected = "<llsd><array><undef /></array></llsd>\n";
- xml_test("1 element array", expected);
-
- mSD.append(1);
- expected = "<llsd><array><undef /><integer>1</integer></array></llsd>\n";
- xml_test("2 element array", expected);
- }
-
- template<> template<>
- void sd_xml_object::test<5>()
- {
- // tests with arrays
- std::string expected;
-
- mSD = LLSD::emptyMap();
- expected = "<llsd><map /></llsd>\n";
- xml_test("empty map", expected);
-
- mSD["foo"] = "bar";
- expected = "<llsd><map><key>foo</key><string>bar</string></map></llsd>\n";
- xml_test("1 element map", expected);
-
- mSD["baz"] = LLSD();
- expected = "<llsd><map><key>baz</key><undef /><key>foo</key><string>bar</string></map></llsd>\n";
- xml_test("2 element map", expected);
- }
-
- template<> template<>
- void sd_xml_object::test<6>()
- {
- // tests with binary
- std::string expected;
-
- // Generated by: echo -n 'hello' | openssl enc -e -base64
- mSD = string_to_vector("hello");
- expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
- xml_test("binary", expected);
-
- mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
- expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
- xml_test("binary", expected);
- }
-
- class TestLLSDSerializeData
- {
- public:
- TestLLSDSerializeData();
- ~TestLLSDSerializeData();
-
- void doRoundTripTests(const std::string&);
- void checkRoundTrip(const std::string&, const LLSD& v);
-
- void setFormatterParser(LLPointer<LLSDFormatter> formatter, LLPointer<LLSDParser> parser)
- {
- mFormatter = [formatter](const LLSD& data, std::ostream& str)
- {
- formatter->format(data, str);
- };
- // this lambda must be mutable since otherwise the bound 'parser'
- // is assumed to point to a const LLSDParser
- mParser = [parser](std::istream& istr, LLSD& data, llssize max_bytes) mutable
- {
- // reset() call is needed since test code re-uses parser object
- parser->reset();
- return (parser->parse(istr, data, max_bytes) > 0);
- };
- }
-
- void setParser(bool (*parser)(LLSD&, std::istream&, llssize))
- {
- // why does LLSDSerialize::deserialize() reverse the parse() params??
- mParser = [parser](std::istream& istr, LLSD& data, llssize max_bytes)
- {
- return parser(data, istr, max_bytes);
- };
- }
-
- FormatterFunction mFormatter;
- ParserFunction mParser;
- };
-
- TestLLSDSerializeData::TestLLSDSerializeData()
- {
- }
-
- TestLLSDSerializeData::~TestLLSDSerializeData()
- {
- }
-
- void TestLLSDSerializeData::checkRoundTrip(const std::string& msg, const LLSD& v)
- {
- std::stringstream stream;
- mFormatter(v, stream);
- //LL_INFOS() << "checkRoundTrip: length " << stream.str().length() << LL_ENDL;
- LLSD w;
- mParser(stream, w, stream.str().size());
-
- try
- {
- ensure_equals(msg, w, v);
- }
- catch (...)
- {
- std::cerr << "the serialized string was:" << std::endl;
- std::cerr << stream.str() << std::endl;
- throw;
- }
- }
-
- static void fillmap(LLSD& root, U32 width, U32 depth)
- {
- if(depth == 0)
- {
- root["foo"] = "bar";
- return;
- }
-
- for(U32 i = 0; i < width; ++i)
- {
- std::string key = llformat("child %d", i);
- root[key] = LLSD::emptyMap();
- fillmap(root[key], width, depth - 1);
- }
- }
-
- void TestLLSDSerializeData::doRoundTripTests(const std::string& msg)
- {
- LLSD v;
- checkRoundTrip(msg + " undefined", v);
-
- v = true;
- checkRoundTrip(msg + " true bool", v);
-
- v = false;
- checkRoundTrip(msg + " false bool", v);
-
- v = 1;
- checkRoundTrip(msg + " positive int", v);
-
- v = 0;
- checkRoundTrip(msg + " zero int", v);
-
- v = -1;
- checkRoundTrip(msg + " negative int", v);
-
- v = 1234.5f;
- checkRoundTrip(msg + " positive float", v);
-
- v = 0.0f;
- checkRoundTrip(msg + " zero float", v);
-
- v = -1234.5f;
- checkRoundTrip(msg + " negative float", v);
-
- // FIXME: need a NaN test
-
- v = LLUUID::null;
- checkRoundTrip(msg + " null uuid", v);
-
- LLUUID newUUID;
- newUUID.generate();
- v = newUUID;
- checkRoundTrip(msg + " new uuid", v);
-
- v = "";
- checkRoundTrip(msg + " empty string", v);
-
- v = "some string";
- checkRoundTrip(msg + " non-empty string", v);
-
- v =
+ struct sd_xml_data
+ {
+ sd_xml_data()
+ {
+ mFormatter = new LLSDXMLFormatter;
+ }
+ LLSD mSD;
+ LLPointer<LLSDXMLFormatter> mFormatter;
+ void xml_test(const char* name, const std::string& expected)
+ {
+ std::ostringstream ostr;
+ mFormatter->format(mSD, ostr);
+ ensure_equals(name, ostr.str(), expected);
+ }
+ };
+
+ typedef test_group<sd_xml_data> sd_xml_test;
+ typedef sd_xml_test::object sd_xml_object;
+ tut::sd_xml_test sd_xml_stream("LLSDXMLFormatter");
+
+ template<> template<>
+ void sd_xml_object::test<1>()
+ {
+ // random atomic tests
+ std::string expected;
+
+ expected = "<llsd><undef /></llsd>\n";
+ xml_test("undef", expected);
+
+ mSD = 3463;
+ expected = "<llsd><integer>3463</integer></llsd>\n";
+ xml_test("integer", expected);
+
+ mSD = "";
+ expected = "<llsd><string /></llsd>\n";
+ xml_test("empty string", expected);
+
+ mSD = "foobar";
+ expected = "<llsd><string>foobar</string></llsd>\n";
+ xml_test("string", expected);
+
+ mSD = LLUUID::null;
+ expected = "<llsd><uuid /></llsd>\n";
+ xml_test("null uuid", expected);
+
+ mSD = LLUUID("c96f9b1e-f589-4100-9774-d98643ce0bed");
+ expected = "<llsd><uuid>c96f9b1e-f589-4100-9774-d98643ce0bed</uuid></llsd>\n";
+ xml_test("uuid", expected);
+
+ mSD = LLURI("https://secondlife.com/login");
+ expected = "<llsd><uri>https://secondlife.com/login</uri></llsd>\n";
+ xml_test("uri", expected);
+
+ mSD = LLDate("2006-04-24T16:11:33Z");
+ expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n";
+ xml_test("date", expected);
+
+ // Generated by: echo -n 'hello' | openssl enc -e -base64
+ std::vector<U8> hello;
+ hello.push_back('h');
+ hello.push_back('e');
+ hello.push_back('l');
+ hello.push_back('l');
+ hello.push_back('o');
+ mSD = hello;
+ expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
+ xml_test("binary", expected);
+ }
+
+ template<> template<>
+ void sd_xml_object::test<2>()
+ {
+ // tests with boolean values.
+ std::string expected;
+
+ mFormatter->boolalpha(true);
+ mSD = true;
+ expected = "<llsd><boolean>true</boolean></llsd>\n";
+ xml_test("bool alpha true", expected);
+ mSD = false;
+ expected = "<llsd><boolean>false</boolean></llsd>\n";
+ xml_test("bool alpha false", expected);
+
+ mFormatter->boolalpha(false);
+ mSD = true;
+ expected = "<llsd><boolean>1</boolean></llsd>\n";
+ xml_test("bool true", expected);
+ mSD = false;
+ expected = "<llsd><boolean>0</boolean></llsd>\n";
+ xml_test("bool false", expected);
+ }
+
+
+ template<> template<>
+ void sd_xml_object::test<3>()
+ {
+ // tests with real values.
+ std::string expected;
+
+ mFormatter->realFormat("%.2f");
+ mSD = 1.0;
+ expected = "<llsd><real>1.00</real></llsd>\n";
+ xml_test("real 1", expected);
+
+ mSD = -34379.0438;
+ expected = "<llsd><real>-34379.04</real></llsd>\n";
+ xml_test("real reduced precision", expected);
+ mFormatter->realFormat("%.4f");
+ expected = "<llsd><real>-34379.0438</real></llsd>\n";
+ xml_test("higher precision", expected);
+
+ mFormatter->realFormat("%.0f");
+ mSD = 0.0;
+ expected = "<llsd><real>0</real></llsd>\n";
+ xml_test("no decimal 0", expected);
+ mSD = 3287.4387;
+ expected = "<llsd><real>3287</real></llsd>\n";
+ xml_test("no decimal real number", expected);
+ }
+
+ template<> template<>
+ void sd_xml_object::test<4>()
+ {
+ // tests with arrays
+ std::string expected;
+
+ mSD = LLSD::emptyArray();
+ expected = "<llsd><array /></llsd>\n";
+ xml_test("empty array", expected);
+
+ mSD.append(LLSD());
+ expected = "<llsd><array><undef /></array></llsd>\n";
+ xml_test("1 element array", expected);
+
+ mSD.append(1);
+ expected = "<llsd><array><undef /><integer>1</integer></array></llsd>\n";
+ xml_test("2 element array", expected);
+ }
+
+ template<> template<>
+ void sd_xml_object::test<5>()
+ {
+ // tests with arrays
+ std::string expected;
+
+ mSD = LLSD::emptyMap();
+ expected = "<llsd><map /></llsd>\n";
+ xml_test("empty map", expected);
+
+ mSD["foo"] = "bar";
+ expected = "<llsd><map><key>foo</key><string>bar</string></map></llsd>\n";
+ xml_test("1 element map", expected);
+
+ mSD["baz"] = LLSD();
+ expected = "<llsd><map><key>baz</key><undef /><key>foo</key><string>bar</string></map></llsd>\n";
+ xml_test("2 element map", expected);
+ }
+
+ template<> template<>
+ void sd_xml_object::test<6>()
+ {
+ // tests with binary
+ std::string expected;
+
+ // Generated by: echo -n 'hello' | openssl enc -e -base64
+ mSD = string_to_vector("hello");
+ expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
+ xml_test("binary", expected);
+
+ mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
+ expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
+ xml_test("binary", expected);
+ }
+
+ class TestLLSDSerializeData
+ {
+ public:
+ TestLLSDSerializeData();
+ ~TestLLSDSerializeData();
+
+ void doRoundTripTests(const std::string&);
+ void checkRoundTrip(const std::string&, const LLSD& v);
+
+ void setFormatterParser(LLPointer<LLSDFormatter> formatter, LLPointer<LLSDParser> parser)
+ {
+ mFormatter = [formatter](const LLSD& data, std::ostream& str)
+ {
+ formatter->format(data, str);
+ };
+ // this lambda must be mutable since otherwise the bound 'parser'
+ // is assumed to point to a const LLSDParser
+ mParser = [parser](std::istream& istr, LLSD& data, llssize max_bytes) mutable
+ {
+ // reset() call is needed since test code re-uses parser object
+ parser->reset();
+ return (parser->parse(istr, data, max_bytes) > 0);
+ };
+ }
+
+ void setParser(bool (*parser)(LLSD&, std::istream&, llssize))
+ {
+ // why does LLSDSerialize::deserialize() reverse the parse() params??
+ mParser = [parser](std::istream& istr, LLSD& data, llssize max_bytes)
+ {
+ return parser(data, istr, max_bytes);
+ };
+ }
+
+ FormatterFunction mFormatter;
+ ParserFunction mParser;
+ };
+
+ TestLLSDSerializeData::TestLLSDSerializeData()
+ {
+ }
+
+ TestLLSDSerializeData::~TestLLSDSerializeData()
+ {
+ }
+
+ void TestLLSDSerializeData::checkRoundTrip(const std::string& msg, const LLSD& v)
+ {
+ std::stringstream stream;
+ mFormatter(v, stream);
+ //LL_INFOS() << "checkRoundTrip: length " << stream.str().length() << LL_ENDL;
+ LLSD w;
+ mParser(stream, w, stream.str().size());
+
+ try
+ {
+ ensure_equals(msg, w, v);
+ }
+ catch (...)
+ {
+ std::cerr << "the serialized string was:" << std::endl;
+ std::cerr << stream.str() << std::endl;
+ throw;
+ }
+ }
+
+ static void fillmap(LLSD& root, U32 width, U32 depth)
+ {
+ if(depth == 0)
+ {
+ root["foo"] = "bar";
+ return;
+ }
+
+ for(U32 i = 0; i < width; ++i)
+ {
+ std::string key = llformat("child %d", i);
+ root[key] = LLSD::emptyMap();
+ fillmap(root[key], width, depth - 1);
+ }
+ }
+
+ void TestLLSDSerializeData::doRoundTripTests(const std::string& msg)
+ {
+ LLSD v;
+ checkRoundTrip(msg + " undefined", v);
+
+ v = true;
+ checkRoundTrip(msg + " true bool", v);
+
+ v = false;
+ checkRoundTrip(msg + " false bool", v);
+
+ v = 1;
+ checkRoundTrip(msg + " positive int", v);
+
+ v = 0;
+ checkRoundTrip(msg + " zero int", v);
+
+ v = -1;
+ checkRoundTrip(msg + " negative int", v);
+
+ v = 1234.5f;
+ checkRoundTrip(msg + " positive float", v);
+
+ v = 0.0f;
+ checkRoundTrip(msg + " zero float", v);
+
+ v = -1234.5f;
+ checkRoundTrip(msg + " negative float", v);
+
+ // FIXME: need a NaN test
+
+ v = LLUUID::null;
+ checkRoundTrip(msg + " null uuid", v);
+
+ LLUUID newUUID;
+ newUUID.generate();
+ v = newUUID;
+ checkRoundTrip(msg + " new uuid", v);
+
+ v = "";
+ checkRoundTrip(msg + " empty string", v);
+
+ v = "some string";
+ checkRoundTrip(msg + " non-empty string", v);
+
+ v =
"Second Life is a 3-D virtual world entirely built and owned by its residents. "
"Since opening to the public in 2003, it has grown explosively and today is "
"inhabited by nearly 100,000 people from around the globe.\n"
@@ -390,437 +388,437 @@ namespace tut
"currency exchanges.\n"
"\n"
"Welcome to Second Life. We look forward to seeing you in-world!\n"
- ;
- checkRoundTrip(msg + " long string", v);
-
- static const U32 block_size = 0x000020;
- for (U32 block = 0x000000; block <= 0x10ffff; block += block_size)
- {
- std::ostringstream out;
-
- for (U32 c = block; c < block + block_size; ++c)
- {
- if (c <= 0x000001f
- && c != 0x000009
- && c != 0x00000a)
- {
- // see XML standard, sections 2.2 and 4.1
- continue;
- }
- if (0x00d800 <= c && c <= 0x00dfff) { continue; }
- if (0x00fdd0 <= c && c <= 0x00fdef) { continue; }
- if ((c & 0x00fffe) == 0x00fffe) { continue; }
- // see Unicode standard, section 15.8
-
- if (c <= 0x00007f)
- {
- out << (char)(c & 0x7f);
- }
- else if (c <= 0x0007ff)
- {
- out << (char)(0xc0 | ((c >> 6) & 0x1f));
- out << (char)(0x80 | ((c >> 0) & 0x3f));
- }
- else if (c <= 0x00ffff)
- {
- out << (char)(0xe0 | ((c >> 12) & 0x0f));
- out << (char)(0x80 | ((c >> 6) & 0x3f));
- out << (char)(0x80 | ((c >> 0) & 0x3f));
- }
- else
- {
- out << (char)(0xf0 | ((c >> 18) & 0x07));
- out << (char)(0x80 | ((c >> 12) & 0x3f));
- out << (char)(0x80 | ((c >> 6) & 0x3f));
- out << (char)(0x80 | ((c >> 0) & 0x3f));
- }
- }
-
- v = out.str();
-
- std::ostringstream blockmsg;
- blockmsg << msg << " unicode string block 0x" << std::hex << block;
- checkRoundTrip(blockmsg.str(), v);
- }
-
- LLDate epoch;
- v = epoch;
- checkRoundTrip(msg + " epoch date", v);
-
- LLDate aDay("2002-12-07T05:07:15.00Z");
- v = aDay;
- checkRoundTrip(msg + " date", v);
-
- LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/");
- v = path;
- checkRoundTrip(msg + " url", v);
-
- const char source[] = "it must be a blue moon again";
- std::vector<U8> data;
- // note, includes terminating '\0'
- copy(&source[0], &source[sizeof(source)], back_inserter(data));
-
- v = data;
- checkRoundTrip(msg + " binary", v);
-
- v = LLSD::emptyMap();
- checkRoundTrip(msg + " empty map", v);
-
- v = LLSD::emptyMap();
- v["name"] = "luke"; //v.insert("name", "luke");
- v["age"] = 3; //v.insert("age", 3);
- checkRoundTrip(msg + " map", v);
-
- v.clear();
- v["a"]["1"] = true;
- v["b"]["0"] = false;
- checkRoundTrip(msg + " nested maps", v);
-
- v = LLSD::emptyArray();
- checkRoundTrip(msg + " empty array", v);
-
- v = LLSD::emptyArray();
- v.append("ali");
- v.append(28);
- checkRoundTrip(msg + " array", v);
-
- v.clear();
- v[0][0] = true;
- v[1][0] = false;
- checkRoundTrip(msg + " nested arrays", v);
-
- v = LLSD::emptyMap();
- fillmap(v, 10, 3); // 10^6 maps
- checkRoundTrip(msg + " many nested maps", v);
- }
-
- typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerializeGroup;
- typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject;
- TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization");
-
- template<> template<>
- void TestLLSDSerializeObject::test<1>()
- {
- setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY),
- new LLSDNotationParser());
- doRoundTripTests("pretty binary notation serialization");
- }
-
- template<> template<>
- void TestLLSDSerializeObject::test<2>()
- {
- setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
- new LLSDNotationParser());
- doRoundTripTests("raw binary notation serialization");
- }
-
- template<> template<>
- void TestLLSDSerializeObject::test<3>()
- {
- setFormatterParser(new LLSDXMLFormatter(), new LLSDXMLParser());
- doRoundTripTests("xml serialization");
- }
-
- template<> template<>
- void TestLLSDSerializeObject::test<4>()
- {
- setFormatterParser(new LLSDBinaryFormatter(), new LLSDBinaryParser());
- doRoundTripTests("binary serialization");
- }
-
- template<> template<>
- void TestLLSDSerializeObject::test<5>()
- {
- mFormatter = [](const LLSD& sd, std::ostream& str)
- {
- LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_BINARY);
- };
- setParser(LLSDSerialize::deserialize);
- doRoundTripTests("serialize(LLSD_BINARY)");
- };
-
- template<> template<>
- void TestLLSDSerializeObject::test<6>()
- {
- mFormatter = [](const LLSD& sd, std::ostream& str)
- {
- LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_XML);
- };
- setParser(LLSDSerialize::deserialize);
- doRoundTripTests("serialize(LLSD_XML)");
- };
-
- template<> template<>
- void TestLLSDSerializeObject::test<7>()
- {
- mFormatter = [](const LLSD& sd, std::ostream& str)
- {
- LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_NOTATION);
- };
- setParser(LLSDSerialize::deserialize);
- // In this test, serialize(LLSD_NOTATION) emits a header recognized by
- // deserialize().
- doRoundTripTests("serialize(LLSD_NOTATION)");
- };
-
- template<> template<>
- void TestLLSDSerializeObject::test<8>()
- {
- setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
- new LLSDNotationParser());
- setParser(LLSDSerialize::deserialize);
- // This is an interesting test because LLSDNotationFormatter does not
- // emit an llsd/notation header.
- doRoundTripTests("LLSDNotationFormatter -> deserialize");
- };
-
- template<> template<>
- void TestLLSDSerializeObject::test<9>()
- {
- setFormatterParser(new LLSDXMLFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
- new LLSDXMLParser());
- setParser(LLSDSerialize::deserialize);
- // This is an interesting test because LLSDXMLFormatter does not
- // emit an LLSD/XML header.
- doRoundTripTests("LLSDXMLFormatter -> deserialize");
- };
+ ;
+ checkRoundTrip(msg + " long string", v);
+
+ static const U32 block_size = 0x000020;
+ for (U32 block = 0x000000; block <= 0x10ffff; block += block_size)
+ {
+ std::ostringstream out;
+
+ for (U32 c = block; c < block + block_size; ++c)
+ {
+ if (c <= 0x000001f
+ && c != 0x000009
+ && c != 0x00000a)
+ {
+ // see XML standard, sections 2.2 and 4.1
+ continue;
+ }
+ if (0x00d800 <= c && c <= 0x00dfff) { continue; }
+ if (0x00fdd0 <= c && c <= 0x00fdef) { continue; }
+ if ((c & 0x00fffe) == 0x00fffe) { continue; }
+ // see Unicode standard, section 15.8
+
+ if (c <= 0x00007f)
+ {
+ out << (char)(c & 0x7f);
+ }
+ else if (c <= 0x0007ff)
+ {
+ out << (char)(0xc0 | ((c >> 6) & 0x1f));
+ out << (char)(0x80 | ((c >> 0) & 0x3f));
+ }
+ else if (c <= 0x00ffff)
+ {
+ out << (char)(0xe0 | ((c >> 12) & 0x0f));
+ out << (char)(0x80 | ((c >> 6) & 0x3f));
+ out << (char)(0x80 | ((c >> 0) & 0x3f));
+ }
+ else
+ {
+ out << (char)(0xf0 | ((c >> 18) & 0x07));
+ out << (char)(0x80 | ((c >> 12) & 0x3f));
+ out << (char)(0x80 | ((c >> 6) & 0x3f));
+ out << (char)(0x80 | ((c >> 0) & 0x3f));
+ }
+ }
+
+ v = out.str();
+
+ std::ostringstream blockmsg;
+ blockmsg << msg << " unicode string block 0x" << std::hex << block;
+ checkRoundTrip(blockmsg.str(), v);
+ }
+
+ LLDate epoch;
+ v = epoch;
+ checkRoundTrip(msg + " epoch date", v);
+
+ LLDate aDay("2002-12-07T05:07:15.00Z");
+ v = aDay;
+ checkRoundTrip(msg + " date", v);
+
+ LLURI path("http://slurl.com/secondlife/Ambleside/57/104/26/");
+ v = path;
+ checkRoundTrip(msg + " url", v);
+
+ const char source[] = "it must be a blue moon again";
+ std::vector<U8> data;
+ // note, includes terminating '\0'
+ copy(&source[0], &source[sizeof(source)], back_inserter(data));
+
+ v = data;
+ checkRoundTrip(msg + " binary", v);
+
+ v = LLSD::emptyMap();
+ checkRoundTrip(msg + " empty map", v);
+
+ v = LLSD::emptyMap();
+ v["name"] = "luke"; //v.insert("name", "luke");
+ v["age"] = 3; //v.insert("age", 3);
+ checkRoundTrip(msg + " map", v);
+
+ v.clear();
+ v["a"]["1"] = true;
+ v["b"]["0"] = false;
+ checkRoundTrip(msg + " nested maps", v);
+
+ v = LLSD::emptyArray();
+ checkRoundTrip(msg + " empty array", v);
+
+ v = LLSD::emptyArray();
+ v.append("ali");
+ v.append(28);
+ checkRoundTrip(msg + " array", v);
+
+ v.clear();
+ v[0][0] = true;
+ v[1][0] = false;
+ checkRoundTrip(msg + " nested arrays", v);
+
+ v = LLSD::emptyMap();
+ fillmap(v, 10, 3); // 10^6 maps
+ checkRoundTrip(msg + " many nested maps", v);
+ }
+
+ typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerializeGroup;
+ typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject;
+ TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization");
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<1>()
+ {
+ setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY),
+ new LLSDNotationParser());
+ doRoundTripTests("pretty binary notation serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<2>()
+ {
+ setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
+ new LLSDNotationParser());
+ doRoundTripTests("raw binary notation serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<3>()
+ {
+ setFormatterParser(new LLSDXMLFormatter(), new LLSDXMLParser());
+ doRoundTripTests("xml serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<4>()
+ {
+ setFormatterParser(new LLSDBinaryFormatter(), new LLSDBinaryParser());
+ doRoundTripTests("binary serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<5>()
+ {
+ mFormatter = [](const LLSD& sd, std::ostream& str)
+ {
+ LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_BINARY);
+ };
+ setParser(LLSDSerialize::deserialize);
+ doRoundTripTests("serialize(LLSD_BINARY)");
+ };
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<6>()
+ {
+ mFormatter = [](const LLSD& sd, std::ostream& str)
+ {
+ LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_XML);
+ };
+ setParser(LLSDSerialize::deserialize);
+ doRoundTripTests("serialize(LLSD_XML)");
+ };
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<7>()
+ {
+ mFormatter = [](const LLSD& sd, std::ostream& str)
+ {
+ LLSDSerialize::serialize(sd, str, LLSDSerialize::LLSD_NOTATION);
+ };
+ setParser(LLSDSerialize::deserialize);
+ // In this test, serialize(LLSD_NOTATION) emits a header recognized by
+ // deserialize().
+ doRoundTripTests("serialize(LLSD_NOTATION)");
+ };
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<8>()
+ {
+ setFormatterParser(new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
+ new LLSDNotationParser());
+ setParser(LLSDSerialize::deserialize);
+ // This is an interesting test because LLSDNotationFormatter does not
+ // emit an llsd/notation header.
+ doRoundTripTests("LLSDNotationFormatter -> deserialize");
+ };
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<9>()
+ {
+ setFormatterParser(new LLSDXMLFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
+ new LLSDXMLParser());
+ setParser(LLSDSerialize::deserialize);
+ // This is an interesting test because LLSDXMLFormatter does not
+ // emit an LLSD/XML header.
+ doRoundTripTests("LLSDXMLFormatter -> deserialize");
+ };
/*==========================================================================*|
- // We do not expect this test to succeed. Without a header, neither
- // notation LLSD nor binary LLSD reliably start with a distinct character,
- // the way XML LLSD starts with '<'. By convention, we default to notation
- // rather than binary.
- template<> template<>
- void TestLLSDSerializeObject::test<10>()
- {
- setFormatterParser(new LLSDBinaryFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
- new LLSDBinaryParser());
- setParser(LLSDSerialize::deserialize);
- // This is an interesting test because LLSDBinaryFormatter does not
- // emit an LLSD/Binary header.
- doRoundTripTests("LLSDBinaryFormatter -> deserialize");
- };
+ // We do not expect this test to succeed. Without a header, neither
+ // notation LLSD nor binary LLSD reliably start with a distinct character,
+ // the way XML LLSD starts with '<'. By convention, we default to notation
+ // rather than binary.
+ template<> template<>
+ void TestLLSDSerializeObject::test<10>()
+ {
+ setFormatterParser(new LLSDBinaryFormatter(false, "", LLSDFormatter::OPTIONS_NONE),
+ new LLSDBinaryParser());
+ setParser(LLSDSerialize::deserialize);
+ // This is an interesting test because LLSDBinaryFormatter does not
+ // emit an LLSD/Binary header.
+ doRoundTripTests("LLSDBinaryFormatter -> deserialize");
+ };
|*==========================================================================*/
- /**
- * @class TestLLSDParsing
- * @brief Base class for of a parse tester.
- */
- template <class parser_t>
- class TestLLSDParsing
- {
- public:
- TestLLSDParsing()
- {
- mParser = new parser_t;
- }
-
- void ensureParse(
- const std::string& msg,
- const std::string& in,
- const LLSD& expected_value,
- S32 expected_count,
- S32 depth_limit = -1)
- {
- std::stringstream input;
- input.str(in);
-
- LLSD parsed_result;
- mParser->reset(); // reset() call is needed since test code re-uses mParser
- S32 parsed_count = mParser->parse(input, parsed_result, in.size(), depth_limit);
- ensure_equals(msg.c_str(), parsed_result, expected_value);
-
- // This count check is really only useful for expected
- // parse failures, since the ensures equal will already
- // require equality.
- std::string count_msg(msg);
- count_msg += " (count)";
- ensure_equals(count_msg, parsed_count, expected_count);
- }
-
- LLPointer<parser_t> mParser;
- };
-
-
- /**
- * @class TestLLSDXMLParsing
- * @brief Concrete instance of a parse tester.
- */
- class TestLLSDXMLParsing : public TestLLSDParsing<LLSDXMLParser>
- {
- public:
- TestLLSDXMLParsing() {}
- };
-
- typedef tut::test_group<TestLLSDXMLParsing> TestLLSDXMLParsingGroup;
- typedef TestLLSDXMLParsingGroup::object TestLLSDXMLParsingObject;
- TestLLSDXMLParsingGroup gTestLLSDXMLParsingGroup("llsd XML parsing");
-
- template<> template<>
- void TestLLSDXMLParsingObject::test<1>()
- {
- // test handling of xml not recognized as llsd results in an
- // LLSD Undefined
- ensureParse(
- "malformed xml",
- "<llsd><string>ha ha</string>",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "not llsd",
- "<html><body><p>ha ha</p></body></html>",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "value without llsd",
- "<string>ha ha</string>",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "key without llsd",
- "<key>ha ha</key>",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
-
- template<> template<>
- void TestLLSDXMLParsingObject::test<2>()
- {
- // test handling of unrecognized or unparseable llsd values
- LLSD v;
- v["amy"] = 23;
- v["bob"] = LLSD();
- v["cam"] = 1.23;
-
- ensureParse(
- "unknown data type",
- "<llsd><map>"
- "<key>amy</key><integer>23</integer>"
- "<key>bob</key><bigint>99999999999999999</bigint>"
- "<key>cam</key><real>1.23</real>"
- "</map></llsd>",
- v,
- v.size() + 1);
- }
-
- template<> template<>
- void TestLLSDXMLParsingObject::test<3>()
- {
- // test handling of nested bad data
-
- LLSD v;
- v["amy"] = 23;
- v["cam"] = 1.23;
-
- ensureParse(
- "map with html",
- "<llsd><map>"
- "<key>amy</key><integer>23</integer>"
- "<html><body>ha ha</body></html>"
- "<key>cam</key><real>1.23</real>"
- "</map></llsd>",
- v,
- v.size() + 1);
-
- v.clear();
- v["amy"] = 23;
- v["cam"] = 1.23;
- ensureParse(
- "map with value for key",
- "<llsd><map>"
- "<key>amy</key><integer>23</integer>"
- "<string>ha ha</string>"
- "<key>cam</key><real>1.23</real>"
- "</map></llsd>",
- v,
- v.size() + 1);
-
- v.clear();
- v["amy"] = 23;
- v["bob"] = LLSD::emptyMap();
- v["cam"] = 1.23;
- ensureParse(
- "map with map of html",
- "<llsd><map>"
- "<key>amy</key><integer>23</integer>"
- "<key>bob</key>"
- "<map>"
- "<html><body>ha ha</body></html>"
- "</map>"
- "<key>cam</key><real>1.23</real>"
- "</map></llsd>",
- v,
- v.size() + 1);
-
- v.clear();
- v[0] = 23;
- v[1] = LLSD();
- v[2] = 1.23;
-
- ensureParse(
- "array value of html",
- "<llsd><array>"
- "<integer>23</integer>"
- "<html><body>ha ha</body></html>"
- "<real>1.23</real>"
- "</array></llsd>",
- v,
- v.size() + 1);
-
- v.clear();
- v[0] = 23;
- v[1] = LLSD::emptyMap();
- v[2] = 1.23;
- ensureParse(
- "array with map of html",
- "<llsd><array>"
- "<integer>23</integer>"
- "<map>"
- "<html><body>ha ha</body></html>"
- "</map>"
- "<real>1.23</real>"
- "</array></llsd>",
- v,
- v.size() + 1);
- }
-
- template<> template<>
- void TestLLSDXMLParsingObject::test<4>()
- {
- // test handling of binary object in XML
- std::string xml;
- LLSD expected;
-
- // Generated by: echo -n 'hello' | openssl enc -e -base64
- expected = string_to_vector("hello");
- xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
- ensureParse(
- "the word 'hello' packed in binary encoded base64",
- xml,
- expected,
- 1);
-
- expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
- xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
- ensureParse(
- "a common binary blob for object -> agent offline inv transfer",
- xml,
- expected,
- 1);
-
- expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
- xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n";
- xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n";
- xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n";
- xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n";
- xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
- ensureParse(
- "a common binary blob for object -> agent offline inv transfer",
- xml,
- expected,
- 1);
- }
+ /**
+ * @class TestLLSDParsing
+ * @brief Base class for of a parse tester.
+ */
+ template <class parser_t>
+ class TestLLSDParsing
+ {
+ public:
+ TestLLSDParsing()
+ {
+ mParser = new parser_t;
+ }
+
+ void ensureParse(
+ const std::string& msg,
+ const std::string& in,
+ const LLSD& expected_value,
+ S32 expected_count,
+ S32 depth_limit = -1)
+ {
+ std::stringstream input;
+ input.str(in);
+
+ LLSD parsed_result;
+ mParser->reset(); // reset() call is needed since test code re-uses mParser
+ S32 parsed_count = mParser->parse(input, parsed_result, in.size(), depth_limit);
+ ensure_equals(msg.c_str(), parsed_result, expected_value);
+
+ // This count check is really only useful for expected
+ // parse failures, since the ensures equal will already
+ // require equality.
+ std::string count_msg(msg);
+ count_msg += " (count)";
+ ensure_equals(count_msg, parsed_count, expected_count);
+ }
+
+ LLPointer<parser_t> mParser;
+ };
+
+
+ /**
+ * @class TestLLSDXMLParsing
+ * @brief Concrete instance of a parse tester.
+ */
+ class TestLLSDXMLParsing : public TestLLSDParsing<LLSDXMLParser>
+ {
+ public:
+ TestLLSDXMLParsing() {}
+ };
+
+ typedef tut::test_group<TestLLSDXMLParsing> TestLLSDXMLParsingGroup;
+ typedef TestLLSDXMLParsingGroup::object TestLLSDXMLParsingObject;
+ TestLLSDXMLParsingGroup gTestLLSDXMLParsingGroup("llsd XML parsing");
+
+ template<> template<>
+ void TestLLSDXMLParsingObject::test<1>()
+ {
+ // test handling of xml not recognized as llsd results in an
+ // LLSD Undefined
+ ensureParse(
+ "malformed xml",
+ "<llsd><string>ha ha</string>",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "not llsd",
+ "<html><body><p>ha ha</p></body></html>",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "value without llsd",
+ "<string>ha ha</string>",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "key without llsd",
+ "<key>ha ha</key>",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+
+ template<> template<>
+ void TestLLSDXMLParsingObject::test<2>()
+ {
+ // test handling of unrecognized or unparseable llsd values
+ LLSD v;
+ v["amy"] = 23;
+ v["bob"] = LLSD();
+ v["cam"] = 1.23;
+
+ ensureParse(
+ "unknown data type",
+ "<llsd><map>"
+ "<key>amy</key><integer>23</integer>"
+ "<key>bob</key><bigint>99999999999999999</bigint>"
+ "<key>cam</key><real>1.23</real>"
+ "</map></llsd>",
+ v,
+ v.size() + 1);
+ }
+
+ template<> template<>
+ void TestLLSDXMLParsingObject::test<3>()
+ {
+ // test handling of nested bad data
+
+ LLSD v;
+ v["amy"] = 23;
+ v["cam"] = 1.23;
+
+ ensureParse(
+ "map with html",
+ "<llsd><map>"
+ "<key>amy</key><integer>23</integer>"
+ "<html><body>ha ha</body></html>"
+ "<key>cam</key><real>1.23</real>"
+ "</map></llsd>",
+ v,
+ v.size() + 1);
+
+ v.clear();
+ v["amy"] = 23;
+ v["cam"] = 1.23;
+ ensureParse(
+ "map with value for key",
+ "<llsd><map>"
+ "<key>amy</key><integer>23</integer>"
+ "<string>ha ha</string>"
+ "<key>cam</key><real>1.23</real>"
+ "</map></llsd>",
+ v,
+ v.size() + 1);
+
+ v.clear();
+ v["amy"] = 23;
+ v["bob"] = LLSD::emptyMap();
+ v["cam"] = 1.23;
+ ensureParse(
+ "map with map of html",
+ "<llsd><map>"
+ "<key>amy</key><integer>23</integer>"
+ "<key>bob</key>"
+ "<map>"
+ "<html><body>ha ha</body></html>"
+ "</map>"
+ "<key>cam</key><real>1.23</real>"
+ "</map></llsd>",
+ v,
+ v.size() + 1);
+
+ v.clear();
+ v[0] = 23;
+ v[1] = LLSD();
+ v[2] = 1.23;
+
+ ensureParse(
+ "array value of html",
+ "<llsd><array>"
+ "<integer>23</integer>"
+ "<html><body>ha ha</body></html>"
+ "<real>1.23</real>"
+ "</array></llsd>",
+ v,
+ v.size() + 1);
+
+ v.clear();
+ v[0] = 23;
+ v[1] = LLSD::emptyMap();
+ v[2] = 1.23;
+ ensureParse(
+ "array with map of html",
+ "<llsd><array>"
+ "<integer>23</integer>"
+ "<map>"
+ "<html><body>ha ha</body></html>"
+ "</map>"
+ "<real>1.23</real>"
+ "</array></llsd>",
+ v,
+ v.size() + 1);
+ }
+
+ template<> template<>
+ void TestLLSDXMLParsingObject::test<4>()
+ {
+ // test handling of binary object in XML
+ std::string xml;
+ LLSD expected;
+
+ // Generated by: echo -n 'hello' | openssl enc -e -base64
+ expected = string_to_vector("hello");
+ xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n";
+ ensureParse(
+ "the word 'hello' packed in binary encoded base64",
+ xml,
+ expected,
+ 1);
+
+ expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
+ xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
+ ensureParse(
+ "a common binary blob for object -> agent offline inv transfer",
+ xml,
+ expected,
+ 1);
+
+ expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0");
+ xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n";
+ xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n";
+ xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n";
+ xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n";
+ xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n";
+ ensureParse(
+ "a common binary blob for object -> agent offline inv transfer",
+ xml,
+ expected,
+ 1);
+ }
template<> template<>
void TestLLSDXMLParsingObject::test<5>()
@@ -858,272 +856,272 @@ namespace tut
}
- /*
- TODO:
- test XML parsing
- binary with unrecognized encoding
- nested LLSD tags
- multiple values inside an LLSD
- */
-
-
- /**
- * @class TestLLSDNotationParsing
- * @brief Concrete instance of a parse tester.
- */
- class TestLLSDNotationParsing : public TestLLSDParsing<LLSDNotationParser>
- {
- public:
- TestLLSDNotationParsing() {}
- };
-
- typedef tut::test_group<TestLLSDNotationParsing> TestLLSDNotationParsingGroup;
- typedef TestLLSDNotationParsingGroup::object TestLLSDNotationParsingObject;
- TestLLSDNotationParsingGroup gTestLLSDNotationParsingGroup(
- "llsd notation parsing");
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<1>()
- {
- // test handling of xml not recognized as llsd results in an
- // LLSD Undefined
- ensureParse(
- "malformed notation map",
- "{'ha ha'",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "malformed notation array",
- "['ha ha'",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "malformed notation string",
- "'ha ha",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "bad notation noise",
- "g48ejlnfr",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<2>()
- {
- ensureParse("valid undef", "!", LLSD(), 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<3>()
- {
- LLSD val = false;
- ensureParse("valid boolean false 0", "false", val, 1);
- ensureParse("valid boolean false 1", "f", val, 1);
- ensureParse("valid boolean false 2", "0", val, 1);
- ensureParse("valid boolean false 3", "F", val, 1);
- ensureParse("valid boolean false 4", "FALSE", val, 1);
- val = true;
- ensureParse("valid boolean true 0", "true", val, 1);
- ensureParse("valid boolean true 1", "t", val, 1);
- ensureParse("valid boolean true 2", "1", val, 1);
- ensureParse("valid boolean true 3", "T", val, 1);
- ensureParse("valid boolean true 4", "TRUE", val, 1);
-
- val.clear();
- ensureParse("invalid true", "TR", val, LLSDParser::PARSE_FAILURE);
- ensureParse("invalid false", "FAL", val, LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<4>()
- {
- LLSD val = 123;
- ensureParse("valid integer", "i123", val, 1);
- val.clear();
- ensureParse("invalid integer", "421", val, LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<5>()
- {
- LLSD val = 456.7;
- ensureParse("valid real", "r456.7", val, 1);
- val.clear();
- ensureParse("invalid real", "456.7", val, LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<6>()
- {
- LLUUID id;
- LLSD val = id;
- ensureParse(
- "unparseable uuid",
- "u123",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- id.generate();
- val = id;
- std::string uuid_str("u");
- uuid_str += id.asString();
- ensureParse("valid uuid", uuid_str.c_str(), val, 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<7>()
- {
- LLSD val = std::string("foolish");
- ensureParse("valid string 1", "\"foolish\"", val, 1);
- val = std::string("g'day");
- ensureParse("valid string 2", "\"g'day\"", val, 1);
- val = std::string("have a \"nice\" day");
- ensureParse("valid string 3", "'have a \"nice\" day'", val, 1);
- val = std::string("whatever");
- ensureParse("valid string 4", "s(8)\"whatever\"", val, 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<8>()
- {
- ensureParse(
- "invalid string 1",
- "s(7)\"whatever\"",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "invalid string 2",
- "s(9)\"whatever\"",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<9>()
- {
- LLSD val = LLURI("http://www.google.com");
- ensureParse("valid uri", "l\"http://www.google.com\"", val, 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<10>()
- {
- LLSD val = LLDate("2007-12-28T09:22:53.10Z");
- ensureParse("valid date", "d\"2007-12-28T09:22:53.10Z\"", val, 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<11>()
- {
- std::vector<U8> vec;
- vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c');
- vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1');
- LLSD val = vec;
- ensureParse("valid binary b64", "b64\"YWJjMzIx\"", val, 1);
- ensureParse("valid bainry b16", "b16\"616263333231\"", val, 1);
- ensureParse("valid bainry raw", "b(6)\"abc321\"", val, 1);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<12>()
- {
- ensureParse(
- "invalid -- binary length specified too long",
- "b(7)\"abc321\"",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "invalid -- binary length specified way too long",
- "b(1000000)\"abc321\"",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<13>()
- {
- LLSD val;
- val["amy"] = 23;
- val["bob"] = LLSD();
- val["cam"] = 1.23;
- ensureParse("simple map", "{'amy':i23,'bob':!,'cam':r1.23}", val, 4);
-
- val["bob"] = LLSD::emptyMap();
- val["bob"]["vehicle"] = std::string("bicycle");
- ensureParse(
- "nested map",
- "{'amy':i23,'bob':{'vehicle':'bicycle'},'cam':r1.23}",
- val,
- 5);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<14>()
- {
- LLSD val;
- val.append(23);
- val.append(LLSD());
- val.append(1.23);
- ensureParse("simple array", "[i23,!,r1.23]", val, 4);
- val[1] = LLSD::emptyArray();
- val[1].append("bicycle");
- ensureParse("nested array", "[i23,['bicycle'],r1.23]", val, 5);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<15>()
- {
- LLSD val;
- val["amy"] = 23;
- val["bob"]["dogs"] = LLSD::emptyArray();
- val["bob"]["dogs"].append(LLSD::emptyMap());
- val["bob"]["dogs"][0]["name"] = std::string("groove");
- val["bob"]["dogs"][0]["breed"] = std::string("samoyed");
- val["bob"]["dogs"].append(LLSD::emptyMap());
- val["bob"]["dogs"][1]["name"] = std::string("greyley");
- val["bob"]["dogs"][1]["breed"] = std::string("chow/husky");
- val["cam"] = 1.23;
- ensureParse(
- "nested notation",
- "{'amy':i23,"
- " 'bob':{'dogs':["
- "{'name':'groove', 'breed':'samoyed'},"
- "{'name':'greyley', 'breed':'chow/husky'}]},"
- " 'cam':r1.23}",
- val,
- 11);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<16>()
- {
- // text to make sure that incorrect sizes bail because
- std::string bad_str("s(5)\"hi\"");
- ensureParse(
- "size longer than bytes left",
- bad_str,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDNotationParsingObject::test<17>()
- {
- // text to make sure that incorrect sizes bail because
- std::string bad_bin("b(5)\"hi\"");
- ensureParse(
- "size longer than bytes left",
- bad_bin,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
+ /*
+ TODO:
+ test XML parsing
+ binary with unrecognized encoding
+ nested LLSD tags
+ multiple values inside an LLSD
+ */
+
+
+ /**
+ * @class TestLLSDNotationParsing
+ * @brief Concrete instance of a parse tester.
+ */
+ class TestLLSDNotationParsing : public TestLLSDParsing<LLSDNotationParser>
+ {
+ public:
+ TestLLSDNotationParsing() {}
+ };
+
+ typedef tut::test_group<TestLLSDNotationParsing> TestLLSDNotationParsingGroup;
+ typedef TestLLSDNotationParsingGroup::object TestLLSDNotationParsingObject;
+ TestLLSDNotationParsingGroup gTestLLSDNotationParsingGroup(
+ "llsd notation parsing");
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<1>()
+ {
+ // test handling of xml not recognized as llsd results in an
+ // LLSD Undefined
+ ensureParse(
+ "malformed notation map",
+ "{'ha ha'",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "malformed notation array",
+ "['ha ha'",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "malformed notation string",
+ "'ha ha",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "bad notation noise",
+ "g48ejlnfr",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<2>()
+ {
+ ensureParse("valid undef", "!", LLSD(), 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<3>()
+ {
+ LLSD val = false;
+ ensureParse("valid boolean false 0", "false", val, 1);
+ ensureParse("valid boolean false 1", "f", val, 1);
+ ensureParse("valid boolean false 2", "0", val, 1);
+ ensureParse("valid boolean false 3", "F", val, 1);
+ ensureParse("valid boolean false 4", "FALSE", val, 1);
+ val = true;
+ ensureParse("valid boolean true 0", "true", val, 1);
+ ensureParse("valid boolean true 1", "t", val, 1);
+ ensureParse("valid boolean true 2", "1", val, 1);
+ ensureParse("valid boolean true 3", "T", val, 1);
+ ensureParse("valid boolean true 4", "TRUE", val, 1);
+
+ val.clear();
+ ensureParse("invalid true", "TR", val, LLSDParser::PARSE_FAILURE);
+ ensureParse("invalid false", "FAL", val, LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<4>()
+ {
+ LLSD val = 123;
+ ensureParse("valid integer", "i123", val, 1);
+ val.clear();
+ ensureParse("invalid integer", "421", val, LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<5>()
+ {
+ LLSD val = 456.7;
+ ensureParse("valid real", "r456.7", val, 1);
+ val.clear();
+ ensureParse("invalid real", "456.7", val, LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<6>()
+ {
+ LLUUID id;
+ LLSD val = id;
+ ensureParse(
+ "unparseable uuid",
+ "u123",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ id.generate();
+ val = id;
+ std::string uuid_str("u");
+ uuid_str += id.asString();
+ ensureParse("valid uuid", uuid_str.c_str(), val, 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<7>()
+ {
+ LLSD val = std::string("foolish");
+ ensureParse("valid string 1", "\"foolish\"", val, 1);
+ val = std::string("g'day");
+ ensureParse("valid string 2", "\"g'day\"", val, 1);
+ val = std::string("have a \"nice\" day");
+ ensureParse("valid string 3", "'have a \"nice\" day'", val, 1);
+ val = std::string("whatever");
+ ensureParse("valid string 4", "s(8)\"whatever\"", val, 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<8>()
+ {
+ ensureParse(
+ "invalid string 1",
+ "s(7)\"whatever\"",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "invalid string 2",
+ "s(9)\"whatever\"",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<9>()
+ {
+ LLSD val = LLURI("http://www.google.com");
+ ensureParse("valid uri", "l\"http://www.google.com\"", val, 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<10>()
+ {
+ LLSD val = LLDate("2007-12-28T09:22:53.10Z");
+ ensureParse("valid date", "d\"2007-12-28T09:22:53.10Z\"", val, 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<11>()
+ {
+ std::vector<U8> vec;
+ vec.push_back((U8)'a'); vec.push_back((U8)'b'); vec.push_back((U8)'c');
+ vec.push_back((U8)'3'); vec.push_back((U8)'2'); vec.push_back((U8)'1');
+ LLSD val = vec;
+ ensureParse("valid binary b64", "b64\"YWJjMzIx\"", val, 1);
+ ensureParse("valid bainry b16", "b16\"616263333231\"", val, 1);
+ ensureParse("valid bainry raw", "b(6)\"abc321\"", val, 1);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<12>()
+ {
+ ensureParse(
+ "invalid -- binary length specified too long",
+ "b(7)\"abc321\"",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "invalid -- binary length specified way too long",
+ "b(1000000)\"abc321\"",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<13>()
+ {
+ LLSD val;
+ val["amy"] = 23;
+ val["bob"] = LLSD();
+ val["cam"] = 1.23;
+ ensureParse("simple map", "{'amy':i23,'bob':!,'cam':r1.23}", val, 4);
+
+ val["bob"] = LLSD::emptyMap();
+ val["bob"]["vehicle"] = std::string("bicycle");
+ ensureParse(
+ "nested map",
+ "{'amy':i23,'bob':{'vehicle':'bicycle'},'cam':r1.23}",
+ val,
+ 5);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<14>()
+ {
+ LLSD val;
+ val.append(23);
+ val.append(LLSD());
+ val.append(1.23);
+ ensureParse("simple array", "[i23,!,r1.23]", val, 4);
+ val[1] = LLSD::emptyArray();
+ val[1].append("bicycle");
+ ensureParse("nested array", "[i23,['bicycle'],r1.23]", val, 5);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<15>()
+ {
+ LLSD val;
+ val["amy"] = 23;
+ val["bob"]["dogs"] = LLSD::emptyArray();
+ val["bob"]["dogs"].append(LLSD::emptyMap());
+ val["bob"]["dogs"][0]["name"] = std::string("groove");
+ val["bob"]["dogs"][0]["breed"] = std::string("samoyed");
+ val["bob"]["dogs"].append(LLSD::emptyMap());
+ val["bob"]["dogs"][1]["name"] = std::string("greyley");
+ val["bob"]["dogs"][1]["breed"] = std::string("chow/husky");
+ val["cam"] = 1.23;
+ ensureParse(
+ "nested notation",
+ "{'amy':i23,"
+ " 'bob':{'dogs':["
+ "{'name':'groove', 'breed':'samoyed'},"
+ "{'name':'greyley', 'breed':'chow/husky'}]},"
+ " 'cam':r1.23}",
+ val,
+ 11);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<16>()
+ {
+ // text to make sure that incorrect sizes bail because
+ std::string bad_str("s(5)\"hi\"");
+ ensureParse(
+ "size longer than bytes left",
+ bad_str,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDNotationParsingObject::test<17>()
+ {
+ // text to make sure that incorrect sizes bail because
+ std::string bad_bin("b(5)\"hi\"");
+ ensureParse(
+ "size longer than bytes left",
+ bad_bin,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
template<> template<>
void TestLLSDNotationParsingObject::test<18>()
{
- LLSD level_1 = LLSD::emptyMap(); level_1["level_2"] = 99;
- LLSD level_0 = LLSD::emptyMap(); level_0["level_1"] = level_1;
+ LLSD level_1 = LLSD::emptyMap(); level_1["level_2"] = 99;
+ LLSD level_0 = LLSD::emptyMap(); level_0["level_1"] = level_1;
LLSD deep = LLSD::emptyMap();
deep["level_0"] = level_0;
@@ -1168,7 +1166,7 @@ namespace tut
template<> template<>
void TestLLSDNotationParsingObject::test<20>()
{
- LLSD end = LLSD::emptyMap(); end["end"] = (S32)99;
+ LLSD end = LLSD::emptyMap(); end["end"] = (S32)99;
LLSD level_49 = LLSD::emptyMap(); level_49["level_49"] = end;
LLSD level_48 = LLSD::emptyMap(); level_48["level_48"] = level_49;
@@ -1259,536 +1257,536 @@ namespace tut
9);
}
- /**
- * @class TestLLSDBinaryParsing
- * @brief Concrete instance of a parse tester.
- */
- class TestLLSDBinaryParsing : public TestLLSDParsing<LLSDBinaryParser>
- {
- public:
- TestLLSDBinaryParsing() {}
- };
-
- typedef tut::test_group<TestLLSDBinaryParsing> TestLLSDBinaryParsingGroup;
- typedef TestLLSDBinaryParsingGroup::object TestLLSDBinaryParsingObject;
- TestLLSDBinaryParsingGroup gTestLLSDBinaryParsingGroup(
- "llsd binary parsing");
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<1>()
- {
- std::vector<U8> vec;
- vec.resize(6);
- vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c';
- vec[3] = '3'; vec[4] = '2'; vec[5] = '1';
- std::string string_expected((char*)&vec[0], vec.size());
- LLSD value = string_expected;
-
- vec.resize(11);
- vec[0] = 's'; // for string
- vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c';
- vec[8] = '3'; vec[9] = '2'; vec[10] = '1';
-
- uint32_t size = htonl(6);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_good((char*)&vec[0], vec.size());
- ensureParse("correct string parse", str_good, value, 1);
-
- size = htonl(7);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_1((char*)&vec[0], vec.size());
- ensureParse(
- "incorrect size string parse",
- str_bad_1,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- size = htonl(100000);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_2((char*)&vec[0], vec.size());
- ensureParse(
- "incorrect size string parse",
- str_bad_2,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<2>()
- {
- std::vector<U8> vec;
- vec.resize(6);
- vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c';
- vec[3] = '3'; vec[4] = '2'; vec[5] = '1';
- LLSD value = vec;
-
- vec.resize(11);
- vec[0] = 'b'; // for binary
- vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c';
- vec[8] = '3'; vec[9] = '2'; vec[10] = '1';
-
- uint32_t size = htonl(6);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_good((char*)&vec[0], vec.size());
- ensureParse("correct binary parse", str_good, value, 1);
-
- size = htonl(7);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_1((char*)&vec[0], vec.size());
- ensureParse(
- "incorrect size binary parse 1",
- str_bad_1,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- size = htonl(100000);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_2((char*)&vec[0], vec.size());
- ensureParse(
- "incorrect size binary parse 2",
- str_bad_2,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<3>()
- {
- // test handling of xml not recognized as llsd results in an
- // LLSD Undefined
- ensureParse(
- "malformed binary map",
- "{'ha ha'",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "malformed binary array",
- "['ha ha'",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "malformed binary string",
- "'ha ha",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- ensureParse(
- "bad noise",
- "g48ejlnfr",
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
- template<> template<>
- void TestLLSDBinaryParsingObject::test<4>()
- {
- ensureParse("valid undef", "!", LLSD(), 1);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<5>()
- {
- LLSD val = false;
- ensureParse("valid boolean false 2", "0", val, 1);
- val = true;
- ensureParse("valid boolean true 2", "1", val, 1);
-
- val.clear();
- ensureParse("invalid true", "t", val, LLSDParser::PARSE_FAILURE);
- ensureParse("invalid false", "f", val, LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<6>()
- {
- std::vector<U8> vec;
- vec.push_back('{');
- vec.resize(vec.size() + 4);
- uint32_t size = htonl(1);
- memcpy(&vec[1], &size, sizeof(uint32_t));
- vec.push_back('k');
- int key_size_loc = vec.size();
- size = htonl(1); // 1 too short
- vec.resize(vec.size() + 4);
- memcpy(&vec[key_size_loc], &size, sizeof(uint32_t));
- vec.push_back('a'); vec.push_back('m'); vec.push_back('y');
- vec.push_back('i');
- int integer_loc = vec.size();
- vec.resize(vec.size() + 4);
- uint32_t val_int = htonl(23);
- memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t));
- std::string str_bad_1((char*)&vec[0], vec.size());
- ensureParse(
- "invalid key size",
- str_bad_1,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- // check with correct size, but unterminated map (missing '}')
- size = htonl(3); // correct size
- memcpy(&vec[key_size_loc], &size, sizeof(uint32_t));
- std::string str_bad_2((char*)&vec[0], vec.size());
- ensureParse(
- "valid key size, unterminated map",
- str_bad_2,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- // check w/ correct size and correct map termination
- LLSD val;
- val["amy"] = 23;
- vec.push_back('}');
- std::string str_good((char*)&vec[0], vec.size());
- ensureParse(
- "valid map",
- str_good,
- val,
- 2);
-
- // check w/ incorrect sizes and correct map termination
- size = htonl(0); // 1 too few (for the map entry)
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_3((char*)&vec[0], vec.size());
- ensureParse(
- "invalid map too long",
- str_bad_3,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- size = htonl(2); // 1 too many
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_4((char*)&vec[0], vec.size());
- ensureParse(
- "invalid map too short",
- str_bad_4,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<7>()
- {
- std::vector<U8> vec;
- vec.push_back('[');
- vec.resize(vec.size() + 4);
- uint32_t size = htonl(1); // 1 too short
- memcpy(&vec[1], &size, sizeof(uint32_t));
- vec.push_back('"'); vec.push_back('a'); vec.push_back('m');
- vec.push_back('y'); vec.push_back('"'); vec.push_back('i');
- int integer_loc = vec.size();
- vec.resize(vec.size() + 4);
- uint32_t val_int = htonl(23);
- memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t));
-
- std::string str_bad_1((char*)&vec[0], vec.size());
- ensureParse(
- "invalid array size",
- str_bad_1,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- // check with correct size, but unterminated map (missing ']')
- size = htonl(2); // correct size
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_2((char*)&vec[0], vec.size());
- ensureParse(
- "unterminated array",
- str_bad_2,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- // check w/ correct size and correct map termination
- LLSD val;
- val.append("amy");
- val.append(23);
- vec.push_back(']');
- std::string str_good((char*)&vec[0], vec.size());
- ensureParse(
- "valid array",
- str_good,
- val,
- 3);
-
- // check with too many elements
- size = htonl(3); // 1 too long
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_bad_3((char*)&vec[0], vec.size());
- ensureParse(
- "array too short",
- str_bad_3,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<8>()
- {
- std::vector<U8> vec;
- vec.push_back('{');
- vec.resize(vec.size() + 4);
- memset(&vec[1], 0, 4);
- vec.push_back('}');
- std::string str_good((char*)&vec[0], vec.size());
- LLSD val = LLSD::emptyMap();
- ensureParse(
- "empty map",
- str_good,
- val,
- 1);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<9>()
- {
- std::vector<U8> vec;
- vec.push_back('[');
- vec.resize(vec.size() + 4);
- memset(&vec[1], 0, 4);
- vec.push_back(']');
- std::string str_good((char*)&vec[0], vec.size());
- LLSD val = LLSD::emptyArray();
- ensureParse(
- "empty array",
- str_good,
- val,
- 1);
- }
-
- template<> template<>
- void TestLLSDBinaryParsingObject::test<10>()
- {
- std::vector<U8> vec;
- vec.push_back('l');
- vec.resize(vec.size() + 4);
- uint32_t size = htonl(14); // 1 too long
- memcpy(&vec[1], &size, sizeof(uint32_t));
- vec.push_back('h'); vec.push_back('t'); vec.push_back('t');
- vec.push_back('p'); vec.push_back(':'); vec.push_back('/');
- vec.push_back('/'); vec.push_back('s'); vec.push_back('l');
- vec.push_back('.'); vec.push_back('c'); vec.push_back('o');
- vec.push_back('m');
- std::string str_bad((char*)&vec[0], vec.size());
- ensureParse(
- "invalid uri length size",
- str_bad,
- LLSD(),
- LLSDParser::PARSE_FAILURE);
-
- LLSD val;
- val = LLURI("http://sl.com");
- size = htonl(13); // correct length
- memcpy(&vec[1], &size, sizeof(uint32_t));
- std::string str_good((char*)&vec[0], vec.size());
- ensureParse(
- "valid key size",
- str_good,
- val,
- 1);
- }
+ /**
+ * @class TestLLSDBinaryParsing
+ * @brief Concrete instance of a parse tester.
+ */
+ class TestLLSDBinaryParsing : public TestLLSDParsing<LLSDBinaryParser>
+ {
+ public:
+ TestLLSDBinaryParsing() {}
+ };
+
+ typedef tut::test_group<TestLLSDBinaryParsing> TestLLSDBinaryParsingGroup;
+ typedef TestLLSDBinaryParsingGroup::object TestLLSDBinaryParsingObject;
+ TestLLSDBinaryParsingGroup gTestLLSDBinaryParsingGroup(
+ "llsd binary parsing");
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<1>()
+ {
+ std::vector<U8> vec;
+ vec.resize(6);
+ vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c';
+ vec[3] = '3'; vec[4] = '2'; vec[5] = '1';
+ std::string string_expected((char*)&vec[0], vec.size());
+ LLSD value = string_expected;
+
+ vec.resize(11);
+ vec[0] = 's'; // for string
+ vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c';
+ vec[8] = '3'; vec[9] = '2'; vec[10] = '1';
+
+ uint32_t size = htonl(6);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_good((char*)&vec[0], vec.size());
+ ensureParse("correct string parse", str_good, value, 1);
+
+ size = htonl(7);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_1((char*)&vec[0], vec.size());
+ ensureParse(
+ "incorrect size string parse",
+ str_bad_1,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ size = htonl(100000);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_2((char*)&vec[0], vec.size());
+ ensureParse(
+ "incorrect size string parse",
+ str_bad_2,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<2>()
+ {
+ std::vector<U8> vec;
+ vec.resize(6);
+ vec[0] = 'a'; vec[1] = 'b'; vec[2] = 'c';
+ vec[3] = '3'; vec[4] = '2'; vec[5] = '1';
+ LLSD value = vec;
+
+ vec.resize(11);
+ vec[0] = 'b'; // for binary
+ vec[5] = 'a'; vec[6] = 'b'; vec[7] = 'c';
+ vec[8] = '3'; vec[9] = '2'; vec[10] = '1';
+
+ uint32_t size = htonl(6);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_good((char*)&vec[0], vec.size());
+ ensureParse("correct binary parse", str_good, value, 1);
+
+ size = htonl(7);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_1((char*)&vec[0], vec.size());
+ ensureParse(
+ "incorrect size binary parse 1",
+ str_bad_1,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ size = htonl(100000);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_2((char*)&vec[0], vec.size());
+ ensureParse(
+ "incorrect size binary parse 2",
+ str_bad_2,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<3>()
+ {
+ // test handling of xml not recognized as llsd results in an
+ // LLSD Undefined
+ ensureParse(
+ "malformed binary map",
+ "{'ha ha'",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "malformed binary array",
+ "['ha ha'",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "malformed binary string",
+ "'ha ha",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ ensureParse(
+ "bad noise",
+ "g48ejlnfr",
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<4>()
+ {
+ ensureParse("valid undef", "!", LLSD(), 1);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<5>()
+ {
+ LLSD val = false;
+ ensureParse("valid boolean false 2", "0", val, 1);
+ val = true;
+ ensureParse("valid boolean true 2", "1", val, 1);
+
+ val.clear();
+ ensureParse("invalid true", "t", val, LLSDParser::PARSE_FAILURE);
+ ensureParse("invalid false", "f", val, LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<6>()
+ {
+ std::vector<U8> vec;
+ vec.push_back('{');
+ vec.resize(vec.size() + 4);
+ uint32_t size = htonl(1);
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ vec.push_back('k');
+ int key_size_loc = vec.size();
+ size = htonl(1); // 1 too short
+ vec.resize(vec.size() + 4);
+ memcpy(&vec[key_size_loc], &size, sizeof(uint32_t));
+ vec.push_back('a'); vec.push_back('m'); vec.push_back('y');
+ vec.push_back('i');
+ int integer_loc = vec.size();
+ vec.resize(vec.size() + 4);
+ uint32_t val_int = htonl(23);
+ memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t));
+ std::string str_bad_1((char*)&vec[0], vec.size());
+ ensureParse(
+ "invalid key size",
+ str_bad_1,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ // check with correct size, but unterminated map (missing '}')
+ size = htonl(3); // correct size
+ memcpy(&vec[key_size_loc], &size, sizeof(uint32_t));
+ std::string str_bad_2((char*)&vec[0], vec.size());
+ ensureParse(
+ "valid key size, unterminated map",
+ str_bad_2,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ // check w/ correct size and correct map termination
+ LLSD val;
+ val["amy"] = 23;
+ vec.push_back('}');
+ std::string str_good((char*)&vec[0], vec.size());
+ ensureParse(
+ "valid map",
+ str_good,
+ val,
+ 2);
+
+ // check w/ incorrect sizes and correct map termination
+ size = htonl(0); // 1 too few (for the map entry)
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_3((char*)&vec[0], vec.size());
+ ensureParse(
+ "invalid map too long",
+ str_bad_3,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ size = htonl(2); // 1 too many
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_4((char*)&vec[0], vec.size());
+ ensureParse(
+ "invalid map too short",
+ str_bad_4,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<7>()
+ {
+ std::vector<U8> vec;
+ vec.push_back('[');
+ vec.resize(vec.size() + 4);
+ uint32_t size = htonl(1); // 1 too short
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ vec.push_back('"'); vec.push_back('a'); vec.push_back('m');
+ vec.push_back('y'); vec.push_back('"'); vec.push_back('i');
+ int integer_loc = vec.size();
+ vec.resize(vec.size() + 4);
+ uint32_t val_int = htonl(23);
+ memcpy(&vec[integer_loc], &val_int, sizeof(uint32_t));
+
+ std::string str_bad_1((char*)&vec[0], vec.size());
+ ensureParse(
+ "invalid array size",
+ str_bad_1,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ // check with correct size, but unterminated map (missing ']')
+ size = htonl(2); // correct size
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_2((char*)&vec[0], vec.size());
+ ensureParse(
+ "unterminated array",
+ str_bad_2,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ // check w/ correct size and correct map termination
+ LLSD val;
+ val.append("amy");
+ val.append(23);
+ vec.push_back(']');
+ std::string str_good((char*)&vec[0], vec.size());
+ ensureParse(
+ "valid array",
+ str_good,
+ val,
+ 3);
+
+ // check with too many elements
+ size = htonl(3); // 1 too long
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_bad_3((char*)&vec[0], vec.size());
+ ensureParse(
+ "array too short",
+ str_bad_3,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<8>()
+ {
+ std::vector<U8> vec;
+ vec.push_back('{');
+ vec.resize(vec.size() + 4);
+ memset(&vec[1], 0, 4);
+ vec.push_back('}');
+ std::string str_good((char*)&vec[0], vec.size());
+ LLSD val = LLSD::emptyMap();
+ ensureParse(
+ "empty map",
+ str_good,
+ val,
+ 1);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<9>()
+ {
+ std::vector<U8> vec;
+ vec.push_back('[');
+ vec.resize(vec.size() + 4);
+ memset(&vec[1], 0, 4);
+ vec.push_back(']');
+ std::string str_good((char*)&vec[0], vec.size());
+ LLSD val = LLSD::emptyArray();
+ ensureParse(
+ "empty array",
+ str_good,
+ val,
+ 1);
+ }
+
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<10>()
+ {
+ std::vector<U8> vec;
+ vec.push_back('l');
+ vec.resize(vec.size() + 4);
+ uint32_t size = htonl(14); // 1 too long
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ vec.push_back('h'); vec.push_back('t'); vec.push_back('t');
+ vec.push_back('p'); vec.push_back(':'); vec.push_back('/');
+ vec.push_back('/'); vec.push_back('s'); vec.push_back('l');
+ vec.push_back('.'); vec.push_back('c'); vec.push_back('o');
+ vec.push_back('m');
+ std::string str_bad((char*)&vec[0], vec.size());
+ ensureParse(
+ "invalid uri length size",
+ str_bad,
+ LLSD(),
+ LLSDParser::PARSE_FAILURE);
+
+ LLSD val;
+ val = LLURI("http://sl.com");
+ size = htonl(13); // correct length
+ memcpy(&vec[1], &size, sizeof(uint32_t));
+ std::string str_good((char*)&vec[0], vec.size());
+ ensureParse(
+ "valid key size",
+ str_good,
+ val,
+ 1);
+ }
/*
- template<> template<>
- void TestLLSDBinaryParsingObject::test<11>()
- {
- }
+ template<> template<>
+ void TestLLSDBinaryParsingObject::test<11>()
+ {
+ }
*/
/**
- * @class TestLLSDCrossCompatible
- * @brief Miscellaneous serialization and parsing tests
- */
- class TestLLSDCrossCompatible
- {
- public:
- TestLLSDCrossCompatible() {}
-
- void ensureBinaryAndNotation(
- const std::string& msg,
- const LLSD& input)
- {
- // to binary, and back again
- std::stringstream str1;
- S32 count1 = LLSDSerialize::toBinary(input, str1);
- LLSD actual_value_bin;
- S32 count2 = LLSDSerialize::fromBinary(
- actual_value_bin,
- str1,
- LLSDSerialize::SIZE_UNLIMITED);
- ensure_equals(
- "ensureBinaryAndNotation binary count",
- count2,
- count1);
-
- // to notation and back again
- std::stringstream str2;
- S32 count3 = LLSDSerialize::toNotation(actual_value_bin, str2);
- ensure_equals(
- "ensureBinaryAndNotation notation count1",
- count3,
- count2);
- LLSD actual_value_notation;
- S32 count4 = LLSDSerialize::fromNotation(
- actual_value_notation,
- str2,
- LLSDSerialize::SIZE_UNLIMITED);
- ensure_equals(
- "ensureBinaryAndNotation notation count2",
- count4,
- count3);
- ensure_equals(
- (msg + " (binaryandnotation)").c_str(),
- actual_value_notation,
- input);
- }
-
- void ensureBinaryAndXML(
- const std::string& msg,
- const LLSD& input)
- {
- // to binary, and back again
- std::stringstream str1;
- S32 count1 = LLSDSerialize::toBinary(input, str1);
- LLSD actual_value_bin;
- S32 count2 = LLSDSerialize::fromBinary(
- actual_value_bin,
- str1,
- LLSDSerialize::SIZE_UNLIMITED);
- ensure_equals(
- "ensureBinaryAndXML binary count",
- count2,
- count1);
-
- // to xml and back again
- std::stringstream str2;
- S32 count3 = LLSDSerialize::toXML(actual_value_bin, str2);
- ensure_equals(
- "ensureBinaryAndXML xml count1",
- count3,
- count2);
- LLSD actual_value_xml;
- S32 count4 = LLSDSerialize::fromXML(actual_value_xml, str2);
- ensure_equals(
- "ensureBinaryAndXML xml count2",
- count4,
- count3);
- ensure_equals((msg + " (binaryandxml)").c_str(), actual_value_xml, input);
- }
- };
-
- typedef tut::test_group<TestLLSDCrossCompatible> TestLLSDCompatibleGroup;
- typedef TestLLSDCompatibleGroup::object TestLLSDCompatibleObject;
- TestLLSDCompatibleGroup gTestLLSDCompatibleGroup(
- "llsd serialize compatible");
-
- template<> template<>
- void TestLLSDCompatibleObject::test<1>()
- {
- LLSD test;
- ensureBinaryAndNotation("undef", test);
- ensureBinaryAndXML("undef", test);
- test = true;
- ensureBinaryAndNotation("boolean true", test);
- ensureBinaryAndXML("boolean true", test);
- test = false;
- ensureBinaryAndNotation("boolean false", test);
- ensureBinaryAndXML("boolean false", test);
- test = 0;
- ensureBinaryAndNotation("integer zero", test);
- ensureBinaryAndXML("integer zero", test);
- test = 1;
- ensureBinaryAndNotation("integer positive", test);
- ensureBinaryAndXML("integer positive", test);
- test = -234567;
- ensureBinaryAndNotation("integer negative", test);
- ensureBinaryAndXML("integer negative", test);
- test = 0.0;
- ensureBinaryAndNotation("real zero", test);
- ensureBinaryAndXML("real zero", test);
- test = 1.0;
- ensureBinaryAndNotation("real positive", test);
- ensureBinaryAndXML("real positive", test);
- test = -1.0;
- ensureBinaryAndNotation("real negative", test);
- ensureBinaryAndXML("real negative", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<2>()
- {
- LLSD test;
- test = "foobar";
- ensureBinaryAndNotation("string", test);
- ensureBinaryAndXML("string", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<3>()
- {
- LLSD test;
- LLUUID id;
- id.generate();
- test = id;
- ensureBinaryAndNotation("uuid", test);
- ensureBinaryAndXML("uuid", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<4>()
- {
- LLSD test;
- test = LLDate(12345.0);
- ensureBinaryAndNotation("date", test);
- ensureBinaryAndXML("date", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<5>()
- {
- LLSD test;
- test = LLURI("http://www.secondlife.com/");
- ensureBinaryAndNotation("uri", test);
- ensureBinaryAndXML("uri", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<6>()
- {
- LLSD test;
- typedef std::vector<U8> buf_t;
- buf_t val;
- for(int ii = 0; ii < 100; ++ii)
- {
- srand(ii); /* Flawfinder: ignore */
- S32 size = rand() % 100 + 10;
- std::generate_n(
- std::back_insert_iterator<buf_t>(val),
- size,
- rand);
- }
- test = val;
- ensureBinaryAndNotation("binary", test);
- ensureBinaryAndXML("binary", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<7>()
- {
- LLSD test;
- test = LLSD::emptyArray();
- test.append(1);
- test.append("hello");
- ensureBinaryAndNotation("array", test);
- ensureBinaryAndXML("array", test);
- }
-
- template<> template<>
- void TestLLSDCompatibleObject::test<8>()
- {
- LLSD test;
- test = LLSD::emptyArray();
- test["foo"] = "bar";
- test["baz"] = 100;
- ensureBinaryAndNotation("map", test);
- ensureBinaryAndXML("map", test);
- }
+ * @class TestLLSDCrossCompatible
+ * @brief Miscellaneous serialization and parsing tests
+ */
+ class TestLLSDCrossCompatible
+ {
+ public:
+ TestLLSDCrossCompatible() {}
+
+ void ensureBinaryAndNotation(
+ const std::string& msg,
+ const LLSD& input)
+ {
+ // to binary, and back again
+ std::stringstream str1;
+ S32 count1 = LLSDSerialize::toBinary(input, str1);
+ LLSD actual_value_bin;
+ S32 count2 = LLSDSerialize::fromBinary(
+ actual_value_bin,
+ str1,
+ LLSDSerialize::SIZE_UNLIMITED);
+ ensure_equals(
+ "ensureBinaryAndNotation binary count",
+ count2,
+ count1);
+
+ // to notation and back again
+ std::stringstream str2;
+ S32 count3 = LLSDSerialize::toNotation(actual_value_bin, str2);
+ ensure_equals(
+ "ensureBinaryAndNotation notation count1",
+ count3,
+ count2);
+ LLSD actual_value_notation;
+ S32 count4 = LLSDSerialize::fromNotation(
+ actual_value_notation,
+ str2,
+ LLSDSerialize::SIZE_UNLIMITED);
+ ensure_equals(
+ "ensureBinaryAndNotation notation count2",
+ count4,
+ count3);
+ ensure_equals(
+ (msg + " (binaryandnotation)").c_str(),
+ actual_value_notation,
+ input);
+ }
+
+ void ensureBinaryAndXML(
+ const std::string& msg,
+ const LLSD& input)
+ {
+ // to binary, and back again
+ std::stringstream str1;
+ S32 count1 = LLSDSerialize::toBinary(input, str1);
+ LLSD actual_value_bin;
+ S32 count2 = LLSDSerialize::fromBinary(
+ actual_value_bin,
+ str1,
+ LLSDSerialize::SIZE_UNLIMITED);
+ ensure_equals(
+ "ensureBinaryAndXML binary count",
+ count2,
+ count1);
+
+ // to xml and back again
+ std::stringstream str2;
+ S32 count3 = LLSDSerialize::toXML(actual_value_bin, str2);
+ ensure_equals(
+ "ensureBinaryAndXML xml count1",
+ count3,
+ count2);
+ LLSD actual_value_xml;
+ S32 count4 = LLSDSerialize::fromXML(actual_value_xml, str2);
+ ensure_equals(
+ "ensureBinaryAndXML xml count2",
+ count4,
+ count3);
+ ensure_equals((msg + " (binaryandxml)").c_str(), actual_value_xml, input);
+ }
+ };
+
+ typedef tut::test_group<TestLLSDCrossCompatible> TestLLSDCompatibleGroup;
+ typedef TestLLSDCompatibleGroup::object TestLLSDCompatibleObject;
+ TestLLSDCompatibleGroup gTestLLSDCompatibleGroup(
+ "llsd serialize compatible");
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<1>()
+ {
+ LLSD test;
+ ensureBinaryAndNotation("undef", test);
+ ensureBinaryAndXML("undef", test);
+ test = true;
+ ensureBinaryAndNotation("boolean true", test);
+ ensureBinaryAndXML("boolean true", test);
+ test = false;
+ ensureBinaryAndNotation("boolean false", test);
+ ensureBinaryAndXML("boolean false", test);
+ test = 0;
+ ensureBinaryAndNotation("integer zero", test);
+ ensureBinaryAndXML("integer zero", test);
+ test = 1;
+ ensureBinaryAndNotation("integer positive", test);
+ ensureBinaryAndXML("integer positive", test);
+ test = -234567;
+ ensureBinaryAndNotation("integer negative", test);
+ ensureBinaryAndXML("integer negative", test);
+ test = 0.0;
+ ensureBinaryAndNotation("real zero", test);
+ ensureBinaryAndXML("real zero", test);
+ test = 1.0;
+ ensureBinaryAndNotation("real positive", test);
+ ensureBinaryAndXML("real positive", test);
+ test = -1.0;
+ ensureBinaryAndNotation("real negative", test);
+ ensureBinaryAndXML("real negative", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<2>()
+ {
+ LLSD test;
+ test = "foobar";
+ ensureBinaryAndNotation("string", test);
+ ensureBinaryAndXML("string", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<3>()
+ {
+ LLSD test;
+ LLUUID id;
+ id.generate();
+ test = id;
+ ensureBinaryAndNotation("uuid", test);
+ ensureBinaryAndXML("uuid", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<4>()
+ {
+ LLSD test;
+ test = LLDate(12345.0);
+ ensureBinaryAndNotation("date", test);
+ ensureBinaryAndXML("date", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<5>()
+ {
+ LLSD test;
+ test = LLURI("http://www.secondlife.com/");
+ ensureBinaryAndNotation("uri", test);
+ ensureBinaryAndXML("uri", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<6>()
+ {
+ LLSD test;
+ typedef std::vector<U8> buf_t;
+ buf_t val;
+ for(int ii = 0; ii < 100; ++ii)
+ {
+ srand(ii); /* Flawfinder: ignore */
+ S32 size = rand() % 100 + 10;
+ std::generate_n(
+ std::back_insert_iterator<buf_t>(val),
+ size,
+ rand);
+ }
+ test = val;
+ ensureBinaryAndNotation("binary", test);
+ ensureBinaryAndXML("binary", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<7>()
+ {
+ LLSD test;
+ test = LLSD::emptyArray();
+ test.append(1);
+ test.append("hello");
+ ensureBinaryAndNotation("array", test);
+ ensureBinaryAndXML("array", test);
+ }
+
+ template<> template<>
+ void TestLLSDCompatibleObject::test<8>()
+ {
+ LLSD test;
+ test = LLSD::emptyArray();
+ test["foo"] = "bar";
+ test["baz"] = 100;
+ ensureBinaryAndNotation("map", test);
+ ensureBinaryAndXML("map", test);
+ }
// helper for TestPythonCompatible
static std::string import_llsd("import os.path\n"
@@ -1921,12 +1919,12 @@ namespace tut
int bufflen{ static_cast<int>(buffstr.length()) };
out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen));
LL_DEBUGS() << "Wrote length: "
- << hexdump(reinterpret_cast<const char*>(&bufflen),
- sizeof(bufflen))
+ << LL::hexdump(reinterpret_cast<const char*>(&bufflen),
+ sizeof(bufflen))
<< LL_ENDL;
out.write(buffstr.c_str(), buffstr.length());
LL_DEBUGS() << "Wrote data: "
- << hexmix(buffstr.c_str(), buffstr.length())
+ << LL::hexmix(buffstr.c_str(), buffstr.length())
<< LL_ENDL;
}
}
@@ -2097,7 +2095,8 @@ namespace tut
NamedTempFile file("llsd", "");
python("Python " + pyformatter,
- [&](std::ostream& out){ out <<
+ [pyformatter, &file](std::ostream& out) {
+ out <<
import_llsd <<
"import struct\n"
"lenformat = struct.Struct('i')\n"
@@ -2130,7 +2129,7 @@ namespace tut
item.asReal(), 3.14, 7); // 7 bits ~= 0.01
ensure("Failed to read LLSD::String from Python",
itemFromStream(inf, item, parse));
- ensure_equals(item.asString(),
+ ensure_equals(item.asString(),
"This string\n"
"has several\n"
"lines.");
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp
index 302bbe6f8d..edccdb097b 100644
--- a/indra/llcommon/threadpool.cpp
+++ b/indra/llcommon/threadpool.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2021-10-21
* @brief Implementation for threadpool.
- *
+ *
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
@@ -18,6 +18,7 @@
// external library headers
// other Linden headers
#include "commoncontrol.h"
+#include "llcoros.h"
#include "llerror.h"
#include "llevents.h"
#include "llsd.h"
@@ -90,20 +91,14 @@ void LL::ThreadPoolBase::start()
return;
}
- // Listen on "LLApp", and when the app is shutting down, close the queue
- // and join the workers.
- LLEventPumps::instance().obtain("LLApp").listen(
+ // When the app is shutting down, close the queue and join the workers.
+ mStopListener = LLCoros::getStopListener(
mName,
- [this](const LLSD& stat)
+ [this](const LLSD& status)
{
- std::string status(stat["status"]);
- if (status != "running")
- {
- // viewer is starting shutdown -- proclaim the end is nigh!
- LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL;
- close();
- }
- return false;
+ // viewer is starting shutdown -- proclaim the end is nigh!
+ LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL;
+ close();
});
}
diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h
index 0eb1891754..7ced7fbf9f 100644
--- a/indra/llcommon/threadpool.h
+++ b/indra/llcommon/threadpool.h
@@ -4,7 +4,7 @@
* @date 2021-10-21
* @brief ThreadPool configures a WorkQueue along with a pool of threads to
* service it.
- *
+ *
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
@@ -13,6 +13,7 @@
#if ! defined(LL_THREADPOOL_H)
#define LL_THREADPOOL_H
+#include "llcoros.h"
#include "threadpool_fwd.h"
#include "workqueue.h"
#include <memory> // std::unique_ptr
@@ -52,8 +53,8 @@ namespace LL
void start();
/**
- * ThreadPool listens for application shutdown messages on the "LLApp"
- * LLEventPump. Call close() to shut down this ThreadPool early.
+ * ThreadPool listens for application shutdown events. Call close() to
+ * shut down this ThreadPool early.
*/
virtual void close();
@@ -95,6 +96,7 @@ namespace LL
std::string mName;
size_t mThreadCount;
+ LLTempBoundListener mStopListener;
};
/**
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index 6066e74fb5..800547084a 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2021-10-06
* @brief Implementation for WorkQueue.
- *
+ *
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
@@ -32,8 +32,11 @@ using Lock = LLCoros::LockType;
LL::WorkQueueBase::WorkQueueBase(const std::string& name):
super(makeName(name))
{
- // TODO: register for "LLApp" events so we can implicitly close() on
- // viewer shutdown.
+ // Register for status change events so we'll implicitly close() on viewer
+ // shutdown.
+ mStopListener = LLCoros::getStopListener(
+ "WorkQueue:" + getKey(),
+ [this](const LLSD&){ close(); });
}
void LL::WorkQueueBase::runUntilClose()
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 9d7bbfbf7a..ecc6f27223 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -3,7 +3,7 @@
* @author Nat Goodspeed
* @date 2021-09-30
* @brief Queue used for inter-thread work passing.
- *
+ *
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
@@ -13,6 +13,7 @@
#define LL_WORKQUEUE_H
#include "llcoros.h"
+#include "llevents.h"
#include "llexception.h"
#include "llinstancetracker.h"
#include "llinstancetrackersubclass.h"
@@ -116,16 +117,29 @@ namespace LL
ARGS&&... args);
/**
- * Post work to another WorkQueue, blocking the calling coroutine
- * until then, returning the result to caller on completion. Optional
- * final argument is TimePoint for WorkSchedule.
+ * Post work, blocking the calling coroutine, returning the result to
+ * caller on completion. Optional final argument is TimePoint for
+ * WorkSchedule.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE, typename... ARGS>
- auto waitForResult(CALLABLE&& callable, ARGS&&... args);
+ auto waitForResult(CALLABLE&& callable, ARGS&&... args)
+ {
+ checkCoroutine("waitForResult()");
+ return waitForResult_(std::forward<CALLABLE>(callable),
+ std::forward<ARGS>(args)...);
+ }
+
+ /**
+ * Post work, blocking the calling coroutine, returning the result to
+ * caller on completion. Optional final argument is TimePoint for
+ * WorkSchedule.
+ */
+ template <typename CALLABLE, typename... ARGS>
+ auto waitForResult_(CALLABLE&& callable, ARGS&&... args);
/*--------------------------- worker API ---------------------------*/
@@ -194,6 +208,8 @@ namespace LL
static std::string makeName(const std::string& name);
void callWork(const Work& work);
+ LLTempBoundListener mStopListener;
+
private:
virtual Work pop_() = 0;
virtual bool tryPop_(Work&) = 0;
@@ -525,7 +541,7 @@ namespace LL
reply,
// Bind the current exception to transport back to the
// originating WorkQueue. Once there, rethrow it.
- [exc = std::current_exception()](){ std::rethrow_exception(exc); });
+ [exc = std::current_exception()]{ std::rethrow_exception(exc); });
}
},
// if caller passed a TimePoint, pass it along to post()
@@ -618,9 +634,8 @@ namespace LL
};
template <typename CALLABLE, typename... ARGS>
- auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args)
+ auto WorkQueueBase::waitForResult_(CALLABLE&& callable, ARGS&&... args)
{
- checkCoroutine("waitForResult()");
// derive callable's return type so we can specialize for void
return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
(this, std::forward<CALLABLE>(callable), std::forward<ARGS>(args)...);