summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2024-07-20 21:54:19 +0800
committerErik Kundiman <erik@megapahit.org>2024-07-20 21:54:19 +0800
commit4cf12a6d223a238ff30f8b334ca91836c7369f88 (patch)
treea2bf8fa27e5fff8a1b8a75cdb5d835f4e959f145 /indra/llcommon
parent13f24f2419edeefbda8bedab01fad21cb97b7445 (diff)
parent569d97707459d5f87cb04a0811c0b7eb1b5c2251 (diff)
Merge branch 'main' into webrtc-voice
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/always_return.h16
-rw-r--r--indra/llcommon/llcoros.cpp55
-rw-r--r--indra/llcommon/llerror.h11
-rw-r--r--indra/llcommon/llexception.cpp44
-rw-r--r--indra/llcommon/llexception.h114
-rw-r--r--indra/llcommon/llprocessor.cpp73
-rw-r--r--indra/llcommon/llsdutil.cpp4
-rw-r--r--indra/llcommon/llsys.cpp71
-rw-r--r--indra/llcommon/tests/llleap_test.cpp2
-rw-r--r--indra/llcommon/tests/llstring_test.cpp4
10 files changed, 259 insertions, 135 deletions
diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h
index a206471da5..b99eb49096 100644
--- a/indra/llcommon/always_return.h
+++ b/indra/llcommon/always_return.h
@@ -79,6 +79,22 @@ namespace LL
DESIRED mDefault;
};
+ // specialize for AlwaysReturn<void>
+ template <>
+ struct AlwaysReturn<void>
+ {
+ public:
+ AlwaysReturn() {}
+
+ // callable returns a type not convertible to DESIRED, return default
+ template <typename CALLABLE, typename... ARGS>
+ void operator()(CALLABLE&& callable, ARGS&&... args)
+ {
+ // discard whatever callable(args) returns
+ std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
+ }
+ };
+
/**
* always_return<T>(some_function, some_args...) calls
* some_function(some_args...). It is guaranteed to return a value of type
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index 8612f9353f..6285ac86ad 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -123,7 +123,7 @@ LLCoros::LLCoros():
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
// empirically this is insufficient.
- mStackSize(1024*1024),
+ mStackSize(512*1024),
// mCurrent does NOT own the current CoroData instance -- it simply
// points to it. So initialize it with a no-op deleter.
mCurrent{ [](CoroData*){} }
@@ -155,7 +155,7 @@ void LLCoros::cleanupSingleton()
// don't use llcoro::suspend() because that module depends
// on this one
// This will yield current(main) thread and will let active
- // corutines run once
+ // coroutines run once
boost::this_fiber::yield();
}
printActiveCoroutines("after pumping");
@@ -286,55 +286,6 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
return name;
}
-namespace
-{
-
-#if LL_WINDOWS
-
-static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
-
-U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop)
-{
- if (code == STATUS_MSC_EXCEPTION)
- {
- // C++ exception, go on
- return EXCEPTION_CONTINUE_SEARCH;
- }
- else
- {
- // handle it
- return EXCEPTION_EXECUTE_HANDLER;
- }
-}
-
-void sehandle(const LLCoros::callable_t& callable)
-{
- __try
- {
- callable();
- }
- __except (exception_filter(GetExceptionCode(), GetExceptionInformation()))
- {
- // convert to C++ styled exception
- // Note: it might be better to use _se_set_translator
- // if you want exception to inherit full callstack
- char integer_string[512];
- sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode());
- throw std::exception(integer_string);
- }
-}
-
-#else // ! LL_WINDOWS
-
-inline void sehandle(const LLCoros::callable_t& callable)
-{
- callable();
-}
-
-#endif // ! LL_WINDOWS
-
-} // anonymous namespace
-
// Top-level wrapper around caller's coroutine callable.
// Normally we like to pass strings and such by const reference -- but in this
// case, we WANT to copy both the name and the callable to our local stack!
@@ -348,7 +299,7 @@ void LLCoros::toplevel(std::string name, callable_t callable)
// run the code the caller actually wants in the coroutine
try
{
- sehandle(callable);
+ LL::seh::catcher(callable);
}
catch (const Stop& exc)
{
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 6176ce0d1d..7353a36c5f 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -95,6 +95,11 @@ const int LL_ERR_NOERR = 0;
#define LL_STATIC_ASSERT(func, msg) static_assert(func, msg)
#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) static_assert(false, msg)
#else
+#if LL_LINUX
+// We need access to raise and SIGSEGV
+#include <signal.h>
+#endif
+
#define LL_STATIC_ASSERT(func, msg) BOOST_STATIC_ASSERT(func)
#define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) BOOST_STATIC_ASSERT(sizeof(type) != 0 && false);
#endif
@@ -408,10 +413,16 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_NEWLINE '\n'
// Use this only in LL_ERRS or in a place that LL_ERRS may not be used
+
+#ifndef LL_LINUX
#define LLERROR_CRASH \
{ \
crashdriver([](int* ptr){ *ptr = 0; exit(*ptr); }); \
}
+#else
+// For Linux we just call raise and be done with it. No fighting the compiler to create a crashing code snippet.
+#define LLERROR_CRASH raise(SIGSEGV );
+#endif
#define LL_ENDL \
LLError::End(); \
diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp
index c0154a569f..107fdc2b2d 100644
--- a/indra/llcommon/llexception.cpp
+++ b/indra/llcommon/llexception.cpp
@@ -15,7 +15,12 @@
#include "llexception.h"
// STL headers
// std headers
+#include <iomanip>
+#include <sstream>
#include <typeinfo>
+#if LL_WINDOWS
+#include <excpt.h>
+#endif // LL_WINDOWS
// external library headers
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception/error_info.hpp>
@@ -29,7 +34,6 @@
// On Windows, header-only implementation causes macro collisions -- use
// prebuilt library
#define BOOST_STACKTRACE_LINK
-#include <excpt.h>
#endif // LL_WINDOWS
#include <boost/stacktrace.hpp>
@@ -94,25 +98,47 @@ void annotate_exception_(boost::exception& exc)
// For windows SEH exception handling we sometimes need a filter that will
// separate C++ exceptions from C SEH exceptions
-static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
+static constexpr U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
+static constexpr U32 STATUS_STACK_FULL = 0xC00000FD;
-U32 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop)
+void LL::seh::fill_stacktrace(std::string& stacktrace, U32 code)
{
- const auto stack = to_string(boost::stacktrace::stacktrace());
- LL_WARNS() << "SEH Exception handled (that probably shouldn't be): Code " << code
- << "\n Stack trace: \n"
- << stack << LL_ENDL;
+ // Sadly, despite its diagnostic importance, trying to capture a
+ // stacktrace when the stack is already blown only terminates us faster.
+ if (code == STATUS_STACK_FULL)
+ {
+ stacktrace = "(stack overflow, no traceback)";
+ }
+ else
+ {
+ stacktrace = to_string(boost::stacktrace::stacktrace());
+ }
+}
+U32 LL::seh::common_filter(U32 code, struct _EXCEPTION_POINTERS*)
+{
if (code == STATUS_MSC_EXCEPTION)
{
- // C++ exception, go on
+ // C++ exception, don't stop at this handler
return EXCEPTION_CONTINUE_SEARCH;
}
else
{
- // handle it
+ // This is a non-C++ exception, e.g. hardware check.
+ // Pass control into the handler block.
return EXCEPTION_EXECUTE_HANDLER;
}
}
+void LL::seh::rethrow(U32 code, const std::string& stacktrace)
+{
+ std::ostringstream out;
+ out << "Windows exception 0x" << std::hex << code;
+ if (! stacktrace.empty())
+ {
+ out << '\n' << stacktrace;
+ }
+ LLTHROW(Windows_SEH_exception(out.str()));
+}
+
#endif //LL_WINDOWS
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
index 68e609444e..f58a553eb3 100644
--- a/indra/llcommon/llexception.h
+++ b/indra/llcommon/llexception.h
@@ -12,6 +12,7 @@
#if ! defined(LL_LLEXCEPTION_H)
#define LL_LLEXCEPTION_H
+#include "always_return.h"
#include <stdexcept>
#include <boost/exception/exception.hpp>
#include <boost/throw_exception.hpp>
@@ -102,14 +103,115 @@ void crash_on_unhandled_exception_(const char*, int, const char*, const std::str
log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)
void log_unhandled_exception_(const char*, int, const char*, const std::string&);
+/*****************************************************************************
+* Structured Exception Handling
+*****************************************************************************/
+// this is used in platform-generic code -- define outside #if LL_WINDOWS
+struct Windows_SEH_exception: public LLException
+{
+ Windows_SEH_exception(const std::string& what): LLException(what) {}
+};
+
+namespace LL
+{
+namespace seh
+{
+
+#if LL_WINDOWS //-------------------------------------------------------------
+
+void fill_stacktrace(std::string& stacktrace, U32 code);
+
+// wrapper around caller's U32 filter(U32 code, struct _EXCEPTION_POINTERS*)
+// filter function: capture a stacktrace, if possible, before forwarding the
+// call to the caller's filter() function
+template <typename FILTER>
+U32 filter_(std::string& stacktrace, FILTER&& filter,
+ U32 code, struct _EXCEPTION_POINTERS* exptrs)
+{
+ // By the time the handler gets control, the stack has been unwound,
+ // so report the stack trace now at filter() time.
+ fill_stacktrace(stacktrace, code);
+ return std::forward<FILTER>(filter)(code, exptrs);
+}
+
+template <typename TRYCODE, typename FILTER, typename HANDLER>
+auto catcher_inner(std::string& stacktrace,
+ TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
+{
+ __try
+ {
+ return std::forward<TRYCODE>(trycode)();
+ }
+ __except (filter_(stacktrace,
+ std::forward<FILTER>(filter),
+ GetExceptionCode(), GetExceptionInformation()))
+ {
+ return always_return<decltype(trycode())>(
+ std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace);
+ }
+}
+
+// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*),
+// handler(U32, const std::string& stacktrace)
+// stacktrace may or may not be available
+template <typename TRYCODE, typename FILTER, typename HANDLER>
+auto catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
+{
+ // Construct and destroy this stacktrace string in the outer function
+ // because we can't do either in the function with __try/__except.
+ std::string stacktrace;
+ return catcher_inner(stacktrace,
+ std::forward<TRYCODE>(trycode),
+ std::forward<FILTER>(filter),
+ std::forward<HANDLER>(handler));
+}
-#if LL_WINDOWS
+// common_filter() handles the typical case in which we want our handler
+// clause to handle only Structured Exceptions rather than explicitly-thrown
+// C++ exceptions
+U32 common_filter(U32 code, struct _EXCEPTION_POINTERS*);
+
+// dyadic variant specifies try(), handler(U32, stacktrace), assumes common_filter()
+template <typename TRYCODE, typename HANDLER>
+auto catcher(TRYCODE&& trycode, HANDLER&& handler)
+{
+ return catcher(std::forward<TRYCODE>(trycode),
+ common_filter,
+ std::forward<HANDLER>(handler));
+}
+
+// monadic variant specifies try(), assumes default filter and handler
+template <typename TRYCODE>
+auto catcher(TRYCODE&& trycode)
+{
+ return catcher(std::forward<TRYCODE>(trycode), rethrow);
+}
+
+[[noreturn]] void rethrow(U32 code, const std::string& stacktrace);
+
+#else // not LL_WINDOWS -----------------------------------------------------
+
+template <typename TRYCODE, typename FILTER, typename HANDLER>
+auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&)
+{
+ return std::forward<TRYCODE>(trycode)();
+}
+
+template <typename TRYCODE, typename HANDLER>
+auto catcher(TRYCODE&& trycode, HANDLER&&)
+{
+ return std::forward<TRYCODE>(trycode)();
+}
+
+template <typename TRYCODE>
+auto catcher(TRYCODE&& trycode)
+{
+ return std::forward<TRYCODE>(trycode)();
+}
-// SEH exception filtering for use in __try __except
-// Separates C++ exceptions from C SEH exceptions
-// Todo: might be good idea to do some kind of seh_to_msc_wrapper(function, ARGS&&);
-U32 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop);
+#endif // not LL_WINDOWS -----------------------------------------------------
-#endif //LL_WINDOWS
+} // namespace LL::seh
+} // namespace LL
#endif /* ! defined(LL_LLEXCEPTION_H) */
diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp
index 35d08d8de9..0e180d9915 100644
--- a/indra/llcommon/llprocessor.cpp
+++ b/indra/llcommon/llprocessor.cpp
@@ -896,19 +896,55 @@ public:
};
#elif LL_LINUX
+
+// *NOTE:Mani - eww, macros! srry.
+#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \
+ if (!cpuinfo[cpuinfo_id].empty()) \
+ { setInfo(llpi_id, cpuinfo[cpuinfo_id]);}
+
+#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \
+ {\
+ S32 result; \
+ if (!cpuinfo[cpuinfo_id].empty() \
+ && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \
+ { setInfo(llpi_id, result);} \
+ }
+
const char CPUINFO_FILE[] = "/proc/cpuinfo";
-class LLProcessorInfoLinuxImpl : public LLProcessorInfoImpl
-{
+class LLProcessorInfoLinuxImpl : public LLProcessorInfoImpl {
public:
- LLProcessorInfoLinuxImpl()
- {
+ LLProcessorInfoLinuxImpl() {
get_proc_cpuinfo();
}
virtual ~LLProcessorInfoLinuxImpl() {}
+
private:
+ F64 getCPUMaxMHZ()
+ {
+ // Nicky: We just look into cpu0. In theory we could iterate over all cores
+ // "/sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_max_freq"
+ // But those should not fluctuate that much?
+ std::ifstream fIn { "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" };
+
+ if( !fIn.is_open() )
+ return 0.0;
+
+ std::string strLine;
+ fIn >> strLine;
+ if( strLine.empty() )
+ return 0.0l;
+
+ F64 mhz {};
+ if( !LLStringUtil::convertToF64(strLine, mhz ) )
+ return 0.0;
+
+ mhz = mhz / 1000.0;
+ return mhz;
+ }
+
void get_proc_cpuinfo()
{
std::map< std::string, std::string > cpuinfo;
@@ -937,30 +973,23 @@ private:
std::string llinename(linename);
LLStringUtil::toLower(llinename);
std::string lineval( spacespot + 1, nlspot );
- cpuinfo[ llinename ] = lineval;
+ cpuinfo[ llinename ] = lineval;
}
fclose(cpuinfo_fp);
}
# if LL_X86
-// *NOTE:Mani - eww, macros! srry.
-#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \
- if (!cpuinfo[cpuinfo_id].empty()) \
- { setInfo(llpi_id, cpuinfo[cpuinfo_id]);}
-
-#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \
- {\
- S32 result; \
- if (!cpuinfo[cpuinfo_id].empty() \
- && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \
- { setInfo(llpi_id, result);} \
+ F64 mhzFromSys = getCPUMaxMHZ();
+ F64 mhzFromProc {};
+ if( !LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhzFromProc ) )
+ mhzFromProc = 0.0;
+ if (mhzFromSys > 1.0 && mhzFromSys > mhzFromProc )
+ {
+ setInfo( eFrequency, mhzFromSys );
}
-
- F64 mhz;
- if (LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhz)
- && 200.0 < mhz && mhz < 10000.0)
+ else if ( 200.0 < mhzFromProc && mhzFromProc < 10000.0)
{
- setInfo(eFrequency,(F64)(mhz));
+ setInfo(eFrequency,(F64)(mhzFromProc));
}
LLPI_SET_INFO_STRING(eBrandName, "model name");
@@ -970,7 +999,7 @@ private:
LLPI_SET_INFO_INT(eModel, "model");
- S32 family = 0;
+ S32 family{};
if (!cpuinfo["cpu family"].empty()
&& LLStringUtil::convertToS32(cpuinfo["cpu family"], family))
{
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index fb6b6bd589..09bfb51cc3 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -161,7 +161,7 @@ LLSD ll_binary_from_string(const LLSD& sd)
char* ll_print_sd(const LLSD& sd)
{
const U32 bufferSize = 10 * 1024;
- static char buffer[bufferSize];
+ static char buffer[bufferSize + 1];
std::ostringstream stream;
//stream.rdbuf()->pubsetbuf(buffer, bufferSize);
stream << LLSDOStreamer<LLSDXMLFormatter>(sd);
@@ -183,7 +183,7 @@ char* ll_pretty_print_sd_ptr(const LLSD* sd)
char* ll_pretty_print_sd(const LLSD& sd)
{
const U32 bufferSize = 100 * 1024;
- static char buffer[bufferSize];
+ static char buffer[bufferSize + 1];
std::ostringstream stream;
//stream.rdbuf()->pubsetbuf(buffer, bufferSize);
stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY);
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index 459fe0c0c8..d21afab050 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -525,57 +525,46 @@ const S32 LLOSInfo::getOSBitness() const
return mOSBitness;
}
-//static
-U32 LLOSInfo::getProcessVirtualSizeKB()
-{
- U32 virtual_size = 0;
-#if LL_LINUX
-# define STATUS_SIZE 2048
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
+namespace {
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
+ U32 readFromProcStat( std::string entryName )
+ {
+ U32 val{};
+#if LL_LINUX
+ constexpr U32 STATUS_SIZE = 2048;
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmSize:");
- if (memp)
+ LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
+ if (status_filep)
{
- numRead += sscanf(memp, "%*s %u", &virtual_size);
+ char buff[STATUS_SIZE]; /* Flawfinder: ignore */
+
+ size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
+ buff[nbytes] = '\0';
+
+ // All these guys return numbers in KB
+ char *memp = strstr(buff, entryName.c_str());
+ if (memp)
+ {
+ (void) sscanf(memp, "%*s %u", &val);
+ }
+ fclose(status_filep);
}
- fclose(status_filep);
- }
#endif
- return virtual_size;
+ return val;
+ }
+
}
//static
-U32 LLOSInfo::getProcessResidentSizeKB()
+U32 LLOSInfo::getProcessVirtualSizeKB()
{
- U32 resident_size = 0;
-#if LL_LINUX
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep != NULL)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
-
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
+ return readFromProcStat( "VmSize:" );
+}
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmRSS:");
- if (memp)
- {
- numRead += sscanf(memp, "%*s %u", &resident_size);
- }
- fclose(status_filep);
- }
-#endif
- return resident_size;
+//static
+U32 LLOSInfo::getProcessResidentSizeKB()
+{
+ return readFromProcStat( "VmRSS:" );
}
//static
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index fa48bcdefd..3fb25b4cef 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -35,7 +35,7 @@
// causes Windows abdominal pain such that it later fails code-signing in some
// mysterious way. Entirely suppressing these LLLeap tests pushes the failure
// rate MUCH lower. Can we re-enable them with a smaller data size on Windows?
-const size_t BUFFERED_LENGTH = 100*1024;
+const size_t BUFFERED_LENGTH = 1023*1024;
#else // not Windows
const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data
diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp
index 3fadfa5334..ea5b0ee5fc 100644
--- a/indra/llcommon/tests/llstring_test.cpp
+++ b/indra/llcommon/tests/llstring_test.cpp
@@ -377,7 +377,7 @@ namespace tut
{
F32 value;
std::string str_val("2147483647"); //0x7FFFFFFF
- ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647);
+ ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647.f);
str_val = "0";
ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0);
@@ -399,7 +399,7 @@ namespace tut
{
F64 value;
std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF
- ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL);
+ ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807.);
str_val = "0";
ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F);