diff options
| author | Anchor <none@none> | 2019-03-05 21:25:24 -0800 | 
|---|---|---|
| committer | Anchor <none@none> | 2019-03-05 21:25:24 -0800 | 
| commit | 2cd1a22587b26e7b034e3100ebf46f244d576bff (patch) | |
| tree | 16435bc171f03102980faa810ed3fa5180b06475 /indra/llcommon | |
| parent | 1b449fa055ea6c85b464bc799f196e0ab3b7026e (diff) | |
| parent | 03db2ddc9c27cf842c6185826617b0da0d2b87f5 (diff) | |
Merge
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | indra/llcommon/llapp.cpp | 23 | ||||
| -rw-r--r-- | indra/llcommon/llcoros.h | 20 | ||||
| -rw-r--r-- | indra/llcommon/llerror.cpp | 160 | ||||
| -rw-r--r-- | indra/llcommon/llerrorcontrol.h | 22 | ||||
| -rw-r--r-- | indra/llcommon/llevents.cpp | 23 | ||||
| -rw-r--r-- | indra/llcommon/llevents.h | 20 | ||||
| -rw-r--r-- | indra/llcommon/llfile.cpp | 19 | ||||
| -rw-r--r-- | indra/llcommon/llinitparam.h | 3 | ||||
| -rw-r--r-- | indra/llcommon/llleap.cpp | 51 | ||||
| -rw-r--r-- | indra/llcommon/llleap.h | 14 | ||||
| -rw-r--r-- | indra/llcommon/llpreprocessor.h | 23 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.cpp | 27 | ||||
| -rw-r--r-- | indra/llcommon/llstring.cpp | 153 | ||||
| -rw-r--r-- | indra/llcommon/llstring.h | 199 | ||||
| -rw-r--r-- | indra/llcommon/stdtypes.h | 7 | ||||
| -rw-r--r-- | indra/llcommon/stringize.h | 52 | ||||
| -rw-r--r-- | indra/llcommon/tests/llerror_test.cpp | 96 | ||||
| -rw-r--r-- | indra/llcommon/tests/llleap_test.cpp | 7 | ||||
| -rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 5 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 7 | ||||
| -rw-r--r-- | indra/llcommon/tests/wrapllerrs.h | 14 | 
22 files changed, 717 insertions, 233 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d9eb13d65a..42ad56f1b0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -255,6 +255,11 @@ set(llcommon_HEADER_FILES  set_source_files_properties(${llcommon_HEADER_FILES}                              PROPERTIES HEADER_FILE_ONLY TRUE) +if (BUGSPLAT_DB) +  set_source_files_properties(llapp.cpp +    PROPERTIES COMPILE_DEFINITIONS "LL_BUGSPLAT") +endif (BUGSPLAT_DB) +  list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES})  if(LLCOMMON_LINK_SHARED) diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 6cc9e804d4..421af3006e 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -392,7 +392,7 @@ void LLApp::setupErrorHandling(bool second_instance)  #if LL_WINDOWS -#if LL_SEND_CRASH_REPORTS +#if LL_SEND_CRASH_REPORTS && ! defined(LL_BUGSPLAT)  	EnableCrashingOnCrashes();  	// This sets a callback to handle w32 signals to the console window. @@ -454,8 +454,15 @@ void LLApp::setupErrorHandling(bool second_instance)  			mExceptionHandler->set_handle_debug_exceptions(true);  		}  	} -#endif -#else +#endif // LL_SEND_CRASH_REPORTS && ! defined(LL_BUGSPLAT) +#else  // ! LL_WINDOWS + +#if defined(LL_BUGSPLAT) +	// Don't install our own signal handlers -- BugSplat needs to hook them, +	// or it's completely ineffectual. +	bool installHandler = false; + +#else // ! LL_BUGSPLAT  	//  	// Start up signal handling.  	// @@ -463,9 +470,11 @@ void LLApp::setupErrorHandling(bool second_instance)  	// thread, asynchronous signals can be delivered to any thread (in theory)  	//  	setup_signals(); -	 +  	// Add google breakpad exception handler configured for Darwin/Linux.  	bool installHandler = true; +#endif // ! LL_BUGSPLAT +  #if LL_DARWIN  	// For the special case of Darwin, we do not want to install the handler if  	// the process is being debugged as the app will exit with value ABRT (6) if @@ -498,7 +507,7 @@ void LLApp::setupErrorHandling(bool second_instance)  		// installing the handler.  		installHandler = true;  	} -	#endif +	#endif // ! LL_RELEASE_FOR_DOWNLOAD  	if(installHandler && (mExceptionHandler == 0))  	{ @@ -514,9 +523,9 @@ void LLApp::setupErrorHandling(bool second_instance)  		google_breakpad::MinidumpDescriptor desc(mDumpPath);  	    mExceptionHandler = new google_breakpad::ExceptionHandler(desc, NULL, unix_minidump_callback, NULL, true, -1);  	} -#endif +#endif // LL_LINUX -#endif +#endif // ! LL_WINDOWS  	startErrorThread();  } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 8fb27af6a4..c551413811 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -170,6 +170,26 @@ public:      static bool get_consuming();      /** +     * RAII control of the consuming flag +     */ +    class OverrideConsuming +    { +    public: +        OverrideConsuming(bool consuming): +            mPrevConsuming(get_consuming()) +        { +            set_consuming(consuming); +        } +        ~OverrideConsuming() +        { +            set_consuming(mPrevConsuming); +        } + +    private: +        bool mPrevConsuming; +    }; + +    /**       * Please do NOT directly use boost::dcoroutines::future! It is essential       * to maintain the "current" coroutine at every context switch. This       * Future wraps the essential boost::dcoroutines::future functionality diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 40eb7d9bac..7cfd1409b1 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -132,8 +132,6 @@ namespace {                      mFile.sync_with_stdio(false);                  }              } -			mWantsTime = true; -            mWantsTags = true;  		}  		~RecordToFile() @@ -175,7 +173,7 @@ namespace {  	public:  		RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)   		{ -			mWantsTime = timestamp; +            this->showMultiline(true);  		}          virtual bool enabled() override @@ -241,7 +239,13 @@ namespace {  	class RecordToFixedBuffer : public LLError::Recorder  	{  	public: -		RecordToFixedBuffer(LLLineBuffer* buffer) : mBuffer(buffer) { } +		RecordToFixedBuffer(LLLineBuffer* buffer) +            : mBuffer(buffer) +            { +                this->showMultiline(true); +                this->showTags(false); +                this->showLocation(false); +            }          virtual bool enabled() override          { @@ -263,7 +267,11 @@ namespace {  	{  	public:  		RecordToWinDebug() -		{} +		{ +            this->showMultiline(true); +            this->showTags(false); +            this->showLocation(false); +        }          virtual bool enabled() override          { @@ -412,6 +420,7 @@ namespace  	public:  		std::ostringstream messageStream;  		bool messageStreamInUse; +		std::string mFatalMessage;  		void addCallSite(LLError::CallSite&);  		void invalidateCallSites(); @@ -454,8 +463,6 @@ namespace LLError  	public:  		virtual ~SettingsConfig(); -		bool                                mPrintLocation; -  		LLError::ELevel                     mDefaultLevel;          bool 								mLogAlwaysFlush; @@ -500,7 +507,6 @@ namespace LLError  	SettingsConfig::SettingsConfig()  		: LLRefCount(), -		mPrintLocation(false),  		mDefaultLevel(LLError::LEVEL_DEBUG),  		mLogAlwaysFlush(true),  		mEnabledLogTypesMask(255), @@ -706,23 +712,22 @@ namespace LLError  		commonInit(user_dir, app_dir, log_to_stderr);  	} -	void setPrintLocation(bool print) +	void setFatalFunction(const FatalFunction& f)  	{  		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -		s->mPrintLocation = print; +		s->mCrashFunction = f;  	} -	void setFatalFunction(const FatalFunction& f) +	FatalFunction getFatalFunction()  	{  		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -		s->mCrashFunction = f; +		return s->mCrashFunction;  	} -    FatalFunction getFatalFunction() -    { -		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -        return s->mCrashFunction; -    } +	std::string getFatalMessage() +	{ +		return Globals::getInstance()->mFatalMessage; +	}  	void setTimeFunction(TimeFunction f)  	{ @@ -845,7 +850,6 @@ namespace LLError  		s->mTagLevelMap.clear();  		s->mUniqueLogMessages.clear(); -		setPrintLocation(config["print-location"]);  		setDefaultLevel(decodeLevel(config["default-level"]));          if (config.has("log-always-flush"))          { @@ -876,11 +880,12 @@ namespace LLError  namespace LLError  {  	Recorder::Recorder() -	:	mWantsTime(false), -		mWantsTags(false), -		mWantsLevel(true), -		mWantsLocation(false), -		mWantsFunctionName(true) +    	: mWantsTime(true) +        , mWantsTags(true) +        , mWantsLevel(true) +        , mWantsLocation(true) +        , mWantsFunctionName(true) +        , mWantsMultiline(false)  	{  	} @@ -917,6 +922,42 @@ namespace LLError  		return mWantsFunctionName;  	} +	// virtual  +	bool Recorder::wantsMultiline()  +	{  +		return mWantsMultiline; +	} + +    void Recorder::showTime(bool show) +    { +        mWantsTime = show; +    } +     +    void Recorder::showTags(bool show) +    { +        mWantsTags = show; +    } + +    void Recorder::showLevel(bool show) +    { +        mWantsLevel = show; +    } + +    void Recorder::showLocation(bool show) +    { +        mWantsLocation = show; +    } + +    void Recorder::showFunctionName(bool show) +    { +        mWantsFunctionName = show; +    } + +    void Recorder::showMultiline(bool show) +    { +        mWantsMultiline = show; +    } +  	void addRecorder(RecorderPtr recorder)  	{  		if (!recorder) @@ -949,17 +990,15 @@ namespace LLError  		s->mFileRecorder.reset();  		s->mFileRecorderFileName.clear(); -		if (file_name.empty()) +		if (!file_name.empty())  		{ -			return; -		} -		 -		RecorderPtr recordToFile(new RecordToFile(file_name)); -		if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay()) -		{ -			s->mFileRecorderFileName = file_name; -			s->mFileRecorder = recordToFile; -			addRecorder(recordToFile); +            RecorderPtr recordToFile(new RecordToFile(file_name)); +            if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay()) +            { +                s->mFileRecorderFileName = file_name; +                s->mFileRecorder = recordToFile; +                addRecorder(recordToFile); +            }  		}  	} @@ -970,14 +1009,12 @@ namespace LLError  		removeRecorder(s->mFixedBufferRecorder);  		s->mFixedBufferRecorder.reset(); -		if (!fixedBuffer) +		if (fixedBuffer)  		{ -			return; -		} -		 -		RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); -		s->mFixedBufferRecorder = recordToFixedBuffer; -		addRecorder(recordToFixedBuffer); +            RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); +            s->mFixedBufferRecorder = recordToFixedBuffer; +            addRecorder(recordToFixedBuffer); +        }  	}  	std::string logFileName() @@ -989,8 +1026,9 @@ namespace LLError  namespace  { -    void addEscapedMessage(std::ostream& out, const std::string& message) +    std::string escapedMessageLines(const std::string& message)      { +        std::ostringstream out;          size_t written_out = 0;          size_t all_content = message.length();          size_t escape_char_index; // always relative to start of message @@ -1026,13 +1064,16 @@ namespace              // write whatever was left              out << message.substr(written_out, std::string::npos);          } +        return out.str();      } -	void writeToRecorders(const LLError::CallSite& site, const std::string& escaped_message) +	void writeToRecorders(const LLError::CallSite& site, const std::string& message)  	{  		LLError::ELevel level = site.mLevel;  		LLError::SettingsConfigPtr s = LLError::Settings::getInstance()->getSettingsConfig(); -	 + +        std::string escaped_message; +          		for (Recorders::const_iterator i = s->mRecorders.begin();  			i != s->mRecorders.end();  			++i) @@ -1064,7 +1105,7 @@ namespace  			}              message_stream << " "; -            if (r->wantsLocation() || level == LLError::LEVEL_ERROR || s->mPrintLocation) +            if (r->wantsLocation() || level == LLError::LEVEL_ERROR)              {                  message_stream << site.mLocationString;              } @@ -1076,7 +1117,18 @@ namespace  			}              message_stream << " : "; -			message_stream << escaped_message; +            if (r->wantsMultiline()) +            { +                message_stream << message; +            } +            else +            { +                if (escaped_message.empty()) +                { +                    escaped_message = escapedMessageLines(message); +                } +                message_stream << escaped_message; +            }  			r->recordMessage(level, message_stream.str());  		} @@ -1320,10 +1372,11 @@ namespace LLError  			delete out;  		} -		std::ostringstream message_stream;  		if (site.mPrintOnce)  		{ +            std::ostringstream message_stream; +  			std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);  			if (messageIter != s->mUniqueLogMessages.end())  			{ @@ -1343,15 +1396,19 @@ namespace LLError  				message_stream << "ONCE: ";  				s->mUniqueLogMessages[message] = 1;  			} +            message_stream << message; +            message = message_stream.str();  		} -		addEscapedMessage(message_stream, message); +		writeToRecorders(site, message); -		writeToRecorders(site, message_stream.str()); -		 -		if (site.mLevel == LEVEL_ERROR  &&  s->mCrashFunction) +		if (site.mLevel == LEVEL_ERROR)  		{ -			s->mCrashFunction(message_stream.str()); +			g->mFatalMessage = message; +			if (s->mCrashFunction) +			{ +				s->mCrashFunction(message); +			}  		}  	}  } @@ -1656,3 +1713,4 @@ bool debugLoggingEnabled(const std::string& tag)  } + diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index 1730f0c640..276d22fc36 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -106,6 +106,9 @@ namespace LLError  	LL_COMMON_API FatalFunction getFatalFunction();  		// Retrieve the previously-set FatalFunction +	LL_COMMON_API std::string getFatalMessage(); +		// Retrieve the message last passed to FatalFunction, if any +  	/// temporarily override the FatalFunction for the duration of a  	/// particular scope, e.g. for unit tests  	class LL_COMMON_API OverrideFatalFunction @@ -151,13 +154,22 @@ namespace LLError  		bool wantsLevel();  		bool wantsLocation();   		bool wantsFunctionName(); +        bool wantsMultiline(); + +		void showTime(bool show); +		void showTags(bool show); +		void showLevel(bool show); +		void showLocation(bool show);  +		void showFunctionName(bool show); +		void showMultiline(bool show);  	protected: -		bool	mWantsTime, -				mWantsTags, -				mWantsLevel, -				mWantsLocation, -				mWantsFunctionName; +		bool mWantsTime; +        bool mWantsTags; +        bool mWantsLevel; +        bool mWantsLocation; +        bool mWantsFunctionName; +        bool mWantsMultiline;  	};  	typedef boost::shared_ptr<Recorder> RecorderPtr; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index dce97b5411..eedd8c92b5 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -545,10 +545,8 @@ bool LLEventStream::post(const LLSD& event)   *****************************************************************************/  bool LLEventMailDrop::post(const LLSD& event)  { -    bool posted = false; -     -    if (!mSignal->empty()) -        posted = LLEventStream::post(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  @@ -564,16 +562,25 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,                                      const NameList& after,                                      const NameList& before)  { -    if (!mEventHistory.empty()) +    // Before actually connecting this listener for subsequent post() calls, +    // first feed each of the saved events, in order, to the new listener. +    // Remove any that this listener consumes -- Effective STL, Item 9. +    for (auto hi(mEventHistory.begin()), hend(mEventHistory.end()); hi != hend; )      { -        if (listener(mEventHistory.front())) +        if (listener(*hi))          { -            mEventHistory.pop_front(); +            // new listener consumed this event, erase it +            hi = mEventHistory.erase(hi); +        } +        else +        { +            // listener did not consume this event, just move along +            ++hi;          }      } +    // let base class perform the actual connection      return LLEventStream::listen_impl(name, listener, after, before); -  } diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 1d51c660ed..5d60c63810 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -650,15 +650,21 @@ public:   *   LLEventMailDrop   *****************************************************************************/  /** - * LLEventMailDrop is a specialization of LLEventStream. Events are posted normally,  - * however if no listeners return that they have handled the event it is placed in  - * a queue. Subsequent attaching listeners will receive stored events from the queue  - * until a listener indicates that the event has been handled.  In order to receive  - * multiple events from a mail drop the listener must disconnect and reconnect. + * LLEventMailDrop is a specialization of LLEventStream. Events are posted + * normally, however if no listener returns that it has handled the event + * (returns true), it is placed in a queue. Subsequent attaching listeners + * will receive stored events from the queue until some listener indicates + * that the event has been handled. + * + * LLEventMailDrop completely decouples the timing of post() calls from + * listen() calls: every event posted to an LLEventMailDrop is eventually seen + * 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 (or LLEventQueue) with a LLEventTimeout or - * LLEventFilter attaching the filter downstream using Timeout's constructor will - * cause the MailDrop to discharge any of it's stored events. The timeout should  + * 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.     * See llcoro::suspendUntilEventOnWithTimeout() for an example.   */ diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index fc203f78e1..8355b1e797 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -30,6 +30,7 @@  #if LL_WINDOWS  #include "llwin32headerslean.h"  #include <stdlib.h>                 // Windows errno +#include <vector>  #else  #include <errno.h>  #endif @@ -134,8 +135,10 @@ int warnif(const std::string& desc, const std::string& filename, int rc, int acc  		{  			// Only do any of this stuff (before LL_ENDL) if it will be logged.  			LL_DEBUGS("LLFile") << empty; -			const char* TEMP = getenv("TEMP"); -			if (! TEMP) +			// would be nice to use LLDir for this, but dependency goes the +			// wrong way +			const char* TEMP = LLFile::tmpdir(); +			if (! (TEMP && *TEMP))  			{  				LL_CONT << "No $TEMP, not running 'handle'";  			} @@ -341,17 +344,13 @@ const char *LLFile::tmpdir()  #if LL_WINDOWS  		sep = '\\'; -		DWORD len = GetTempPathW(0, L""); -		llutf16string utf16path; -		utf16path.resize(len + 1); -		len = GetTempPathW(static_cast<DWORD>(utf16path.size()), &utf16path[0]); -		utf8path = utf16str_to_utf8str(utf16path); +		std::vector<wchar_t> utf16path(MAX_PATH + 1); +		GetTempPathW(utf16path.size(), &utf16path[0]); +		utf8path = ll_convert_wide_to_string(&utf16path[0]);  #else  		sep = '/'; -		char *env = getenv("TMPDIR"); - -		utf8path = env ? env : "/tmp/"; +		utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/");  #endif  		if (utf8path[utf8path.size() - 1] != sep)  		{ diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index f1f4226c40..7f5b9b4ac2 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -2115,6 +2115,9 @@ namespace LLInitParam  			typedef typename super_t::iterator										iterator;  			typedef typename super_t::const_iterator								const_iterator; +			using super_t::operator(); +			using super_t::operator const container_t&; +  			explicit Multiple(const char* name = "")  			:	super_t(DERIVED_BLOCK::getBlockDescriptor(), name, container_t(), &validate, RANGE::minCount, RANGE::maxCount)  			{} diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index c87d2a3e58..cf8f8cc6a5 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -47,9 +47,9 @@ class LLLeapImpl: public LLLeap      LOG_CLASS(LLLeap);  public:      // Called only by LLLeap::create() -    LLLeapImpl(const std::string& desc, const std::vector<std::string>& plugin): +    LLLeapImpl(const LLProcess::Params& cparams):          // We might reassign mDesc in the constructor body if it's empty here. -        mDesc(desc), +        mDesc(cparams.desc),          // We expect multiple LLLeapImpl instances. Definitely tweak          // mDonePump's name for uniqueness.          mDonePump("LLLeap", true), @@ -67,17 +67,17 @@ public:          // this class or method name.          mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))      { -        // Rule out empty vector -        if (plugin.empty()) +        // Rule out unpopulated Params block +        if (! cparams.executable.isProvided())          {              LLTHROW(Error("no plugin command"));          }          // Don't leave desc empty either, but in this case, if we weren't          // given one, we'll fake one. -        if (desc.empty()) +        if (mDesc.empty())          { -            mDesc = LLProcess::basename(plugin[0]); +            mDesc = LLProcess::basename(cparams.executable);              // how about a toLower() variant that returns the transformed string?!              std::string desclower(mDesc);              LLStringUtil::toLower(desclower); @@ -87,9 +87,9 @@ public:              // notice Python specially: we provide Python LLSD serialization              // support, so there's a pretty good reason to implement plugins              // in that language. -            if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe")) +            if (cparams.args.size() && (desclower == "python" || desclower == "python.exe"))              { -                mDesc = LLProcess::basename(plugin[1]); +                mDesc = LLProcess::basename(cparams.args()[0]);              }          } @@ -97,14 +97,10 @@ public:          mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));          // Okay, launch child. -        LLProcess::Params params; +        // Get a modifiable copy of params block to set files and postend. +        LLProcess::Params params(cparams); +        // copy our deduced mDesc back into the params block          params.desc = mDesc; -        std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end()); -        params.executable = *pi++; -        for ( ; pi != pend; ++pi) -        { -            params.args.add(*pi); -        }          params.files.add(LLProcess::FileParam("pipe")); // stdin          params.files.add(LLProcess::FileParam("pipe")); // stdout          params.files.add(LLProcess::FileParam("pipe")); // stderr @@ -429,17 +425,17 @@ private:      boost::scoped_ptr<LLLeapListener> mListener;  }; -// This must follow the declaration of LLLeapImpl, so it may as well be last. -LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc) +// These must follow the declaration of LLLeapImpl, so they may as well be last. +LLLeap* LLLeap::create(const LLProcess::Params& params, bool exc)  {      // If caller is willing to permit exceptions, just instantiate.      if (exc) -        return new LLLeapImpl(desc, plugin); +        return new LLLeapImpl(params);      // Caller insists on suppressing LLLeap::Error. Very well, catch it.      try      { -        return new LLLeapImpl(desc, plugin); +        return new LLLeapImpl(params);      }      catch (const LLLeap::Error&)      { @@ -447,6 +443,23 @@ LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>&      }  } +LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc) +{ +    LLProcess::Params params; +    params.desc = desc; +    std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end()); +    // could validate here, but let's rely on LLLeapImpl's constructor +    if (pi != pend) +    { +        params.executable = *pi++; +    } +    for ( ; pi != pend; ++pi) +    { +        params.args.add(*pi); +    } +    return create(params, exc); +} +  LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc)  {      // Use LLStringUtil::getTokens() to parse the command line diff --git a/indra/llcommon/llleap.h b/indra/llcommon/llleap.h index 8aac8a64c5..7cecdf2f8f 100644 --- a/indra/llcommon/llleap.h +++ b/indra/llcommon/llleap.h @@ -14,6 +14,7 @@  #include "llinstancetracker.h"  #include "llexception.h" +#include "llprocess.h"  #include <string>  #include <vector> @@ -62,6 +63,19 @@ public:                            bool exc=true);      /** +     * Pass an LLProcess::Params instance to specify desc, executable, args et al. +     * +     * Note that files and postend are set implicitly; any values you set in +     * those fields will be disregarded. +     * +     * Pass exc=false to suppress LLLeap::Error exception. Obviously in that +     * case the caller cannot discover the nature of the error, merely that an +     * error of some kind occurred (because create() returned NULL). Either +     * way, the error is logged. +     */ +    static LLLeap* create(const LLProcess::Params& params, bool exc=true); + +    /**       * Exception thrown for invalid create() arguments, e.g. no plugin       * program. This is more resiliant than an LL_ERRS failure, because the       * string(s) passed to create() might come from an external source. This diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 2879038c36..e8f9981437 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -101,6 +101,9 @@  #endif +// Although thread_local is now a standard storage class, we can't just +// #define LL_THREAD_LOCAL as thread_local because the *usage* is different. +// We'll have to take the time to change LL_THREAD_LOCAL declarations by hand.  #if LL_WINDOWS  # define LL_THREAD_LOCAL __declspec(thread)  #else @@ -177,6 +180,24 @@  #define LL_DLLIMPORT  #endif // LL_WINDOWS +#if ! defined(LL_WINDOWS) +#define LL_WCHAR_T_NATIVE 1 +#else  // LL_WINDOWS +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros +// _WCHAR_T_DEFINED is defined if wchar_t is provided at all. +// Specifically, it has value 1 if wchar_t is an intrinsic type, else empty. +// _NATIVE_WCHAR_T_DEFINED has value 1 if wchar_t is intrinsic, else undefined. +// For years we have compiled with /Zc:wchar_t-, meaning that wchar_t is a +// typedef for unsigned short (in stddef.h). Lore has it that one of our +// proprietary binary-only libraries has traditionally been built that way and +// therefore EVERYTHING ELSE requires it. Therefore, in a typical Linden +// Windows build, _WCHAR_T_DEFINED is defined but empty, while +// _NATIVE_WCHAR_T_DEFINED is undefined. +# if defined(_NATIVE_WCHAR_T_DEFINED) +#  define LL_WCHAR_T_NATIVE 1 +# endif // _NATIVE_WCHAR_T_DEFINED +#endif // LL_WINDOWS +  #if LL_COMMON_LINK_SHARED  // CMake automagically defines llcommon_EXPORTS only when building llcommon  // sources, and only when llcommon is a shared library (i.e. when @@ -198,6 +219,8 @@  #define LL_TO_STRING_HELPER(x) #x  #define LL_TO_STRING(x) LL_TO_STRING_HELPER(x) +#define LL_TO_WSTRING_HELPER(x) L#x +#define LL_TO_WSTRING(x) LL_TO_WSTRING_HELPER(x)  #define LL_FILE_LINENO_MSG(msg) __FILE__ "(" LL_TO_STRING(__LINE__) ") : " msg  #define LL_GLUE_IMPL(x, y) x##y  #define LL_GLUE_TOKENS(x, y) LL_GLUE_IMPL(x, y) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 5753efdc59..1fa53f322b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1205,30 +1205,9 @@ static LLProcess::Status interpret_status(int status)  /// GetLastError()/FormatMessage() boilerplate  static std::string WindowsErrorString(const std::string& operation)  { -	int result = GetLastError(); - -	LPTSTR error_str = 0; -	if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, -					   NULL, -					   result, -					   0, -					   (LPTSTR)&error_str, -					   0, -					   NULL) -		!= 0)  -	{ -		// convert from wide-char string to multi-byte string -		char message[256]; -		wcstombs(message, error_str, sizeof(message)); -		message[sizeof(message)-1] = 0; -		LocalFree(error_str); -		// convert to std::string to trim trailing whitespace -		std::string mbsstr(message); -		mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); -		return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); -	} -	return STRINGIZE(operation << " failed (" << result -					 << "), but FormatMessage() did not explain"); +	auto result = GetLastError(); +	return STRINGIZE(operation << " failed (" << result << "): " +					 << windows_message<std::string>(result));  }  /***************************************************************************** diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 9a02fecd72..0174c411b4 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -30,6 +30,7 @@  #include "llerror.h"  #include "llfasttimer.h"  #include "llsd.h" +#include <vector>  #if LL_WINDOWS  #include "llwin32headerslean.h" @@ -672,6 +673,11 @@ namespace snprintf_hack  	}  } +std::string ll_convert_wide_to_string(const wchar_t* in) +{ +	return ll_convert_wide_to_string(in, CP_UTF8); +} +  std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)  {  	std::string out; @@ -709,7 +715,12 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)  	return out;  } -wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page) +std::wstring ll_convert_string_to_wide(const std::string& in) +{ +	return ll_convert_string_to_wide(in, CP_UTF8); +} + +std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_page)  {  	// From review:  	// We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, @@ -719,28 +730,148 @@ wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page  	// but we *are* seeing string operations taking a bunch of time, especially when constructing widgets.  //	int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); -	// reserve place to NULL terminator -	int output_str_len = in.length(); -	wchar_t* w_out = new wchar_t[output_str_len + 1]; +	// reserve an output buffer that will be destroyed on exit, with a place +	// to put NULL terminator +	std::vector<wchar_t> w_out(in.length() + 1); -	memset(w_out, 0, output_str_len + 1); -	int real_output_str_len = MultiByteToWideChar (code_page, 0, in.c_str(), in.length(), w_out, output_str_len); +	memset(&w_out[0], 0, w_out.size()); +	int real_output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), +												  &w_out[0], w_out.size() - 1);  	//looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858.  	w_out[real_output_str_len] = 0; -	return w_out; +	// construct string<wchar_t> from our temporary output buffer +	return {&w_out[0]}; +} + +LLWString ll_convert_wide_to_wstring(const std::wstring& in) +{ +    // This function, like its converse, is a placeholder, encapsulating a +    // guilty little hack: the only "official" way nat has found to convert +    // between std::wstring (16 bits on Windows) and LLWString (UTF-32) is +    // by using iconv, which we've avoided so far. It kinda sorta works to +    // just copy individual characters... +    // The point is that if/when we DO introduce some more official way to +    // perform such conversions, we should only have to call it here. +    return { in.begin(), in.end() }; +} + +std::wstring ll_convert_wstring_to_wide(const LLWString& in) +{ +    // See comments in ll_convert_wide_to_wstring() +    return { in.begin(), in.end() };  }  std::string ll_convert_string_to_utf8_string(const std::string& in)  { -	wchar_t* w_mesg = ll_convert_string_to_wide(in, CP_ACP); -	std::string out_utf8(ll_convert_wide_to_string(w_mesg, CP_UTF8)); -	delete[] w_mesg; +	auto w_mesg = ll_convert_string_to_wide(in, CP_ACP); +	std::string out_utf8(ll_convert_wide_to_string(w_mesg.c_str(), CP_UTF8));  	return out_utf8;  } -#endif // LL_WINDOWS + +namespace +{ + +void HeapFree_deleter(void* ptr) +{ +    // instead of LocalFree(), per https://stackoverflow.com/a/31541205 +    HeapFree(GetProcessHeap(), NULL, ptr); +} + +} // anonymous namespace + +template<> +std::wstring windows_message<std::wstring>(DWORD error) +{ +    // derived from https://stackoverflow.com/a/455533 +    wchar_t* rawptr = nullptr; +    auto okay = FormatMessageW( +        // use system message tables for GetLastError() codes +        FORMAT_MESSAGE_FROM_SYSTEM | +        // internally allocate buffer and return its pointer +        FORMAT_MESSAGE_ALLOCATE_BUFFER | +        // you cannot pass insertion parameters (thanks Gandalf) +        FORMAT_MESSAGE_IGNORE_INSERTS | +        // ignore line breaks in message definition text +        FORMAT_MESSAGE_MAX_WIDTH_MASK, +        NULL,                       // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM +        error,                      // dwMessageId +        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId +        (LPWSTR)&rawptr,         // lpBuffer: force-cast wchar_t** to wchar_t* +        0,                // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER +        NULL);            // Arguments, unused + +    // make a unique_ptr from rawptr so it gets cleaned up properly +    std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter); + +    if (okay && bufferptr) +    { +        // got the message, return it ('okay' is length in characters) +        return { bufferptr.get(), okay }; +    } + +    // did not get the message, synthesize one +    auto format_message_error = GetLastError(); +    std::wostringstream out; +    out << L"GetLastError() " << error << L" (FormatMessageW() failed with " +        << format_message_error << L")"; +    return out.str(); +} + +boost::optional<std::wstring> llstring_getoptenv(const std::string& key) +{ +    auto wkey = ll_convert_string_to_wide(key); +    // Take a wild guess as to how big the buffer should be. +    std::vector<wchar_t> buffer(1024); +    auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); +    // If our initial guess was too short, n will indicate the size (in +    // wchar_t's) that buffer should have been, including the terminating nul. +    if (n > (buffer.size() - 1)) +    { +        // make it big enough +        buffer.resize(n); +        // and try again +        n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); +    } +    // did that (ultimately) succeed? +    if (n) +    { +        // great, return populated boost::optional +        return boost::optional<std::wstring>(&buffer[0]); +    } + +    // not successful +    auto last_error = GetLastError(); +    // Don't bother warning for NOT_FOUND; that's an expected case +    if (last_error != ERROR_ENVVAR_NOT_FOUND) +    { +        LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " +                   << windows_message<std::string>(last_error) << LL_ENDL; +    } +    // return empty boost::optional +    return {}; +} + +#else  // ! LL_WINDOWS + +boost::optional<std::string> llstring_getoptenv(const std::string& key) +{ +    auto found = getenv(key.c_str()); +    if (found) +    { +        // return populated boost::optional +        return boost::optional<std::string>(found); +    } +    else +    { +        // return empty boost::optional +        return {}; +    } +} + +#endif // ! LL_WINDOWS  long LLStringOps::sPacificTimeOffset = 0;  long LLStringOps::sLocalTimeOffset = 0; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 68ee9db46b..30bec3a6f8 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -27,6 +27,7 @@  #ifndef LL_LLSTRING_H  #define LL_LLSTRING_H +#include <boost/optional/optional.hpp>  #include <string>  #include <cstdio>  //#include <locale> @@ -337,6 +338,19 @@ public:  		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); @@ -496,6 +510,37 @@ LL_COMMON_API bool iswindividual(llwchar elem);   * Unicode support   */ +/// generic conversion aliases +template<typename TO, typename FROM, typename Enable=void> +struct ll_convert_impl +{ +    // Don't even provide a generic implementation. We specialize for every +    // combination we do support. +    TO operator()(const FROM& in) const; +}; + +// Use a function template to get the nice ll_convert<TO>(from_value) API. +template<typename TO, typename FROM> +TO ll_convert(const FROM& in) +{ +    return ll_convert_impl<TO, FROM>()(in); +} + +// degenerate case +template<typename T> +struct ll_convert_impl<T, T> +{ +    T operator()(const T& in) const { return in; } +}; + +// specialize ll_convert_impl<TO, FROM> to return EXPR +#define ll_convert_alias(TO, FROM, EXPR)                    \ +template<>                                                  \ +struct ll_convert_impl<TO, FROM>                            \ +{                                                           \ +    TO operator()(const FROM& in) const { return EXPR; }    \ +} +  // Make the incoming string a utf8 string. Replaces any unknown glyph  // with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest  // of the data may not be recovered. @@ -503,30 +548,88 @@ LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);  //  // We should never use UTF16 except when communicating with Win32! +// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because +// the Windows APIs we want to call are all defined in terms of wchar_t* +// (or worse, LPCTSTR). +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types + +// While there is no point coding for an ASCII-only world (! defined(UNICODE)), +// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going +// forward, we should code in terms of wchar_t and std::wstring so as to +// support either setting of /Zc:wchar_t. + +// The first link above states that char can be used to hold ASCII or any +// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t +// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: +// * char and std::string always hold UTF-8 (of which ASCII is a subset). It +//   is a BUG if they are used to pass strings in any other multi-byte +//   encoding. +// * wchar_t and std::wstring should be our interface to Windows wide-string +//   APIs, and therefore hold UTF-16LE. +// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do +//   not introduce new uses of U16 or llutf16string for string data. +// * llwchar and LLWString hold UTF-32 strings. +// * Do not introduce char16_t or std::u16string. +// * Do not introduce char32_t or std::u32string.  // +// This typedef may or may not be identical to std::wstring, depending on +// LL_WCHAR_T_NATIVE.  typedef std::basic_string<U16> llutf16string; +#if ! defined(LL_WCHAR_T_NATIVE) +// wchar_t is identical to U16, and std::wstring is identical to llutf16string. +// Defining an ll_convert alias involving llutf16string would collide with the +// comparable preferred alias involving std::wstring. (In this scenario, if +// you pass llutf16string, it will engage the std::wstring specialization.) +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +#else  // defined(LL_WCHAR_T_NATIVE) +// wchar_t is a distinct native type, so llutf16string is also a distinct +// type, and there IS a point to converting separately to/from llutf16string. +// (But why? Windows APIs are still defined in terms of wchar_t, and +// in this scenario llutf16string won't work for them!) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#if LL_WINDOWS +// LL_WCHAR_T_NATIVE is defined on non-Windows systems because, in fact, +// wchar_t is native. Everywhere but Windows, we use it for llwchar (see +// stdtypes.h). That makes LLWString identical to std::wstring, so these +// aliases for std::wstring would collide with those for LLWString. Only +// define on Windows, where converting between std::wstring and llutf16string +// means copying chars. +ll_convert_alias(llutf16string, std::wstring, llutf16string(in.begin(), in.end())); +ll_convert_alias(std::wstring, llutf16string,  std::wstring(in.begin(), in.end())); +#endif // LL_WINDOWS +#endif // defined(LL_WCHAR_T_NATIVE) +  LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len);  LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); +ll_convert_u16_alias(LLWString, llutf16string, utf16str_to_wstring(in));  LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len);  LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); +ll_convert_u16_alias(llutf16string, LLWString, wstring_to_utf16str(in));  LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len);  LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +ll_convert_u16_alias(llutf16string, std::string, utf8str_to_utf16str(in));  LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len);  LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str);  // Same function, better name. JC  inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } +// best name of all +ll_convert_alias(LLWString, std::string, utf8string_to_wstring(in));  //  LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars);  LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len);  LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); +ll_convert_alias(std::string, LLWString, wstring_to_utf8str(in));  LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len);  LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); +ll_convert_u16_alias(std::string, llutf16string, utf16str_to_utf8str(in));  #if LL_WINDOWS  inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} @@ -635,22 +738,77 @@ using snprintf_hack::snprintf;   * This replaces the unsafe W2A macro from ATL.   */  LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); // default CP_UTF8 +inline std::string ll_convert_wide_to_string(const std::wstring& in, unsigned int code_page) +{ +    return ll_convert_wide_to_string(in.c_str(), code_page); +} +inline std::string ll_convert_wide_to_string(const std::wstring& in) +{ +    return ll_convert_wide_to_string(in.c_str()); +} +ll_convert_alias(std::string, std::wstring, ll_convert_wide_to_string(in));  /**   * Converts a string to wide string. - * - * It will allocate memory for result string with "new []". Don't forget to release it with "delete []".   */ -LL_COMMON_API wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in, +                                                     unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in); +                                                     // default CP_UTF8 +ll_convert_alias(std::wstring, std::string, ll_convert_string_to_wide(in));  /** - * Converts incoming string into urf8 string + * Convert a Windows wide string to our LLWString + */ +LL_COMMON_API LLWString ll_convert_wide_to_wstring(const std::wstring& in); +ll_convert_alias(LLWString, std::wstring, ll_convert_wide_to_wstring(in)); + +/** + * Convert LLWString to Windows wide string + */ +LL_COMMON_API std::wstring ll_convert_wstring_to_wide(const LLWString& in); +ll_convert_alias(std::wstring, LLWString, ll_convert_wstring_to_wide(in)); + +/** + * Converts incoming string into utf8 string   *   */  LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); +/// Get Windows message string for passed GetLastError() code +// VS 2013 doesn't let us forward-declare this template, which is what we +// started with, so the implementation could reference the specialization we +// haven't yet declared. Somewhat weirdly, just stating the generic +// implementation in terms of the specialization works, even in this order... + +// the general case is just a conversion from the sole implementation +// Microsoft says DWORD is a typedef for unsigned long +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// so rather than drag windows.h into everybody's include space... +template<typename STRING> +STRING windows_message(unsigned long error) +{ +    return ll_convert<STRING>(windows_message<std::wstring>(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template<typename STRING> +STRING windows_message() { return windows_message<STRING>(GetLastError()); } +  //@} -#endif // LL_WINDOWS + +LL_COMMON_API boost::optional<std::wstring> llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API boost::optional<std::string>  llstring_getoptenv(const std::string& key); + +#endif // ! LL_WINDOWS  /**   * Many of the 'strip' and 'replace' methods of LLStringUtilBase need @@ -1593,6 +1751,37 @@ bool LLStringUtilBase<T>::endsWith(  	return (idx == (string.size() - substr.size()));  } +// static +template<class T> +auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<string_type> +{ +    auto found(llstring_getoptenv(key)); +    if (found) +    { +        // return populated boost::optional +        return { ll_convert<string_type>(*found) }; +    } +    else +    { +        // empty boost::optional +        return {}; +    } +} + +// static +template<class T> +auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type +{ +    auto found(getoptenv(key)); +    if (found) +    { +        return *found; +    } +    else +    { +        return dflt; +    } +}  template<class T>   BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value) diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index bf3f3f9ee8..6c9871e76c 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -37,7 +37,12 @@ typedef signed int			S32;  typedef unsigned int			U32;  #if LL_WINDOWS -// Windows wchar_t is 16-bit +// https://docs.microsoft.com/en-us/cpp/build/reference/zc-wchar-t-wchar-t-is-native-type +// https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp +// Windows wchar_t is 16-bit, whichever way /Zc:wchar_t is set. In effect, +// Windows wchar_t is always a typedef, either for unsigned short or __wchar_t. +// (__wchar_t, available either way, is Microsoft's native 2-byte wchar_t type.) +// In any case, llwchar should be a UTF-32 type.  typedef U32				llwchar;  #else  typedef wchar_t				llwchar; diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index a5a90d7297..38dd198ad3 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -30,7 +30,6 @@  #define LL_STRINGIZE_H  #include <sstream> -#include <boost/phoenix/phoenix.hpp>  #include <llstring.h>  /** @@ -53,12 +52,7 @@ std::basic_string<CHARTYPE> gstringize(const T& item)   */  inline std::string stringize(const std::wstring& item)  { -    LL_WARNS() << "WARNING:  Possible narrowing" << LL_ENDL; -     -    std::string s; -     -    s = wstring_to_utf8str(item); -    return gstringize<char>(s); +    return wstring_to_utf8str(item);  }  /** @@ -76,7 +70,10 @@ std::string stringize(const T& item)   */  inline std::wstring wstringize(const std::string& item)  { -    return gstringize<wchar_t>(item.c_str()); +    // utf8str_to_wstring() returns LLWString, which isn't necessarily the +    // same as std::wstring +    LLWString s(utf8str_to_wstring(item)); +    return std::wstring(s.begin(), s.end());  }  /** @@ -91,10 +88,10 @@ std::wstring wstringize(const T& item)  /**   * stringize_f(functor)   */ -template <typename Functor> -std::string stringize_f(Functor const & f) +template <typename CHARTYPE, typename Functor> +std::basic_string<CHARTYPE> stringize_f(Functor const & f)  { -    std::ostringstream out; +    std::basic_ostringstream<CHARTYPE> out;      f(out);      return out.str();  } @@ -108,31 +105,37 @@ std::string stringize_f(Functor const & f)   * return out.str();   * @endcode   */ -#define STRINGIZE(EXPRESSION) (stringize_f(boost::phoenix::placeholders::arg1 << EXPRESSION)) +#define STRINGIZE(EXPRESSION) (stringize_f<char>([&](std::ostream& out){ out << EXPRESSION; })) +/** + * WSTRINGIZE() is the wstring equivalent of STRINGIZE() + */ +#define WSTRINGIZE(EXPRESSION) (stringize_f<wchar_t>([&](std::wostream& out){ out << EXPRESSION; }))  /**   * destringize(str)   * defined for symmetry with stringize - * *NOTE - this has distinct behavior from boost::lexical_cast<T> regarding + * @NOTE - this has distinct behavior from boost::lexical_cast<T> regarding   * leading/trailing whitespace and handling of bad_lexical_cast exceptions + * @NOTE - no need for dewstringize(), since passing std::wstring will Do The + * Right Thing   */ -template <typename T> -T destringize(std::string const & str) +template <typename T, typename CHARTYPE> +T destringize(std::basic_string<CHARTYPE> const & str)  { -	T val; -    std::istringstream in(str); -	in >> val; +    T val; +    std::basic_istringstream<CHARTYPE> in(str); +    in >> val;      return val;  }  /**   * destringize_f(str, functor)   */ -template <typename Functor> -void destringize_f(std::string const & str, Functor const & f) +template <typename CHARTYPE, typename Functor> +void destringize_f(std::basic_string<CHARTYPE> const & str, Functor const & f)  { -    std::istringstream in(str); +    std::basic_istringstream<CHARTYPE> in(str);      f(in);  } @@ -143,8 +146,11 @@ void destringize_f(std::string const & str, Functor const & f)   * std::istringstream in(str);   * in >> item1 >> item2 >> item3 ... ;   * @endcode + * @NOTE - once we get generic lambdas, we shouldn't need DEWSTRINGIZE() any + * more since DESTRINGIZE() should do the right thing with a std::wstring. But + * until then, the lambda we pass must accept the right std::basic_istream.   */ -#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), (boost::phoenix::placeholders::arg1 >> EXPRESSION))) - +#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::istream& in){in >> EXPRESSION;})) +#define DEWSTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::wistream& in){in >> EXPRESSION;}))  #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index ce0dbce075..8e1f4c14ac 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -78,8 +78,12 @@ namespace tut  	class TestRecorder : public LLError::Recorder  	{  	public: -		TestRecorder() { mWantsTime = false; mWantsTags = true; } -		virtual ~TestRecorder() {  } +		TestRecorder() +            { +                showTime(false); +            } +		virtual ~TestRecorder() +            {}  		virtual void recordMessage(LLError::ELevel level,  						   const std::string& message) @@ -90,8 +94,6 @@ namespace tut  		int countMessages()			{ return (int) mMessages.size(); }  		void clearMessages()		{ mMessages.clear(); } -		void setWantsTime(bool t)	{ mWantsTime = t; } -  		std::string message(int n)  		{  			std::ostringstream test_name; @@ -139,9 +141,14 @@ namespace tut  		}  		void setWantsTime(bool t) -		{ -			boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->setWantsTime(t); -		} +            { +                boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->showTime(t); +            } + +		void setWantsMultiline(bool t) +            { +                boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->showMultiline(t); +            }  		std::string message(int n)  		{ @@ -378,27 +385,6 @@ namespace  	}  } -namespace tut -{ -	template<> template<> -	void ErrorTestObject::test<5>() -		// file and line information in log messages -	{ -		std::string location = writeReturningLocation(); -			// expecting default to not print location information - -		LLError::setPrintLocation(true); -		writeReturningLocation(); - -		LLError::setPrintLocation(false); -		writeReturningLocation(); - -		ensure_message_does_not_contain(0, location); -		ensure_message_field_equals(1, LOCATION_FIELD, location); -		ensure_message_does_not_contain(2, location); -	} -} -  /* 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 @@ -512,6 +498,39 @@ namespace  	}  } +namespace +{ +    void writeMsgNeedsEscaping() +    { +        LL_DEBUGS("WriteTag") << "backslash\\" << LL_ENDL; +        LL_INFOS("WriteTag") << "newline\nafternewline" << LL_ENDL; +        LL_WARNS("WriteTag") << "return\rafterreturn" << LL_ENDL; + +        LL_DEBUGS("WriteTag") << "backslash\\backslash\\" << LL_ENDL; +        LL_INFOS("WriteTag") << "backslash\\newline\nanothernewline\nafternewline" << LL_ENDL; +        LL_WARNS("WriteTag") << "backslash\\returnnewline\r\n\\afterbackslash" << LL_ENDL; +    } +}; + +namespace tut +{ +    template<> template<> +    void ErrorTestObject::test<5>() +        // backslash, return, and newline are not escaped with backslashes +    { +        LLError::setDefaultLevel(LLError::LEVEL_DEBUG); +        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"); +        ensure_message_field_equals(2, MSG_FIELD, "return\rafterreturn"); +        ensure_message_field_equals(3, MSG_FIELD, "backslash\\backslash\\"); +        ensure_message_field_equals(4, MSG_FIELD, "backslash\\newline\nanothernewline\nafternewline"); +        ensure_message_field_equals(5, MSG_FIELD, "backslash\\returnnewline\r\n\\afterbackslash"); +        ensure_message_count(6); +    } +} +  namespace tut  {  	template<> template<> @@ -583,7 +602,6 @@ namespace tut  		// special handling of LL_ERRS() calls  	void ErrorTestObject::test<8>()  	{ -		LLError::setPrintLocation(false);  		std::string location = errorReturningLocation();  		ensure_message_field_equals(0, LOCATION_FIELD, location); @@ -630,15 +648,15 @@ namespace tut  		// output order  	void ErrorTestObject::test<10>()  	{ -		LLError::setPrintLocation(true);  		LLError::setTimeFunction(roswell);  		setWantsTime(true); +  		std::string location,  					function;  		writeReturningLocationAndFunction(location, function);  		ensure_equals("order is time level tags location function message", -			message(0), +                      message(0),                        roswell() + " INFO " + "# " /* no tag */ + location + " " + function + " : " + "apple");  	} @@ -658,7 +676,7 @@ namespace tut  		LLError::setTimeFunction(roswell);  		LLError::RecorderPtr anotherRecorder(new TestRecorder()); -		boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->setWantsTime(true); +		boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->showTime(true);  		LLError::addRecorder(anotherRecorder);  		LL_INFOS() << "baz" << LL_ENDL; @@ -835,20 +853,6 @@ namespace tut  	}  } -namespace -{ -    void writeMsgNeedsEscaping() -    { -        LL_DEBUGS("WriteTag") << "backslash\\" << LL_ENDL; -        LL_INFOS("WriteTag") << "newline\nafternewline" << LL_ENDL; -        LL_WARNS("WriteTag") << "return\rafterreturn" << LL_ENDL; - -        LL_DEBUGS("WriteTag") << "backslash\\backslash\\" << LL_ENDL; -        LL_INFOS("WriteTag") << "backslash\\newline\nanothernewline\nafternewline" << LL_ENDL; -        LL_WARNS("WriteTag") << "backslash\\returnnewline\r\n\\afterbackslash" << LL_ENDL; -    } -}; -  namespace tut  {      template<> template<> diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index c387da6c48..45648536c4 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -26,6 +26,7 @@  #include "wrapllerrs.h"  #include "llevents.h"  #include "llprocess.h" +#include "llstring.h"  #include "stringize.h"  #include "StringVec.h"  #include <functional> @@ -198,14 +199,12 @@ namespace tut              // basename.              reader_module(LLProcess::basename(                                reader.getName().substr(0, reader.getName().length()-3))), -            pPYTHON(getenv("PYTHON")), -            PYTHON(pPYTHON? pPYTHON : "") +            PYTHON(LLStringUtil::getenv("PYTHON"))          { -            ensure("Set PYTHON to interpreter pathname", pPYTHON); +            ensure("Set PYTHON to interpreter pathname", !PYTHON.empty());          }          NamedExtTempFile reader;          const std::string reader_module; -        const char* pPYTHON;          const std::string PYTHON;      };      typedef test_group<llleap_data> llleap_group; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index b27e125d2e..5c87cdabd9 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -34,6 +34,7 @@  #include "stringize.h"  #include "llsdutil.h"  #include "llevents.h" +#include "llstring.h"  #include "wrapllerrs.h"  #if defined(LL_WINDOWS) @@ -142,8 +143,8 @@ struct PythonProcessLauncher          mDesc(desc),          mScript("py", script)      { -        const char* PYTHON(getenv("PYTHON")); -        tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); +        auto PYTHON(LLStringUtil::getenv("PYTHON")); +        tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());          mParams.desc = desc + " script";          mParams.executable = PYTHON; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 745e3a168c..6ac974e659 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -41,6 +41,7 @@ typedef U32 uint32_t;  #include <sys/stat.h>  #include <sys/wait.h>  #include "llprocess.h" +#include "llstring.h"  #endif  #include "boost/range.hpp" @@ -1705,8 +1706,8 @@ namespace tut          template <typename CONTENT>          void python(const std::string& desc, const CONTENT& script, int expect=0)          { -            const char* PYTHON(getenv("PYTHON")); -            ensure("Set $PYTHON to the Python interpreter", PYTHON); +            auto PYTHON(LLStringUtil::getenv("PYTHON")); +            ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());              NamedTempFile scriptfile("py", script); @@ -1714,7 +1715,7 @@ namespace tut              std::string q("\"");              std::string qPYTHON(q + PYTHON + q);              std::string qscript(q + scriptfile.getName() + q); -            int rc = _spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL); +            int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL);              if (rc == -1)              {                  char buffer[256]; diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 9a4bbbd630..08fbf19b1c 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -109,6 +109,12 @@ public:          mMessages.push_back(message);      } +    friend inline +    std::ostream& operator<<(std::ostream& out, const CaptureLogRecorder& log) +    { +        return log.streamto(out); +    } +      /// Don't assume the message we want is necessarily the LAST log message      /// emitted by the underlying code; search backwards through all messages      /// for the sought string. @@ -126,7 +132,7 @@ public:          throw tut::failure(STRINGIZE("failed to find '" << search                                       << "' in captured log messages:\n" -                                     << boost::ref(*this))); +                                     << *this));      }      std::ostream& streamto(std::ostream& out) const @@ -200,10 +206,4 @@ private:  	LLError::RecorderPtr mRecorder;  }; -inline -std::ostream& operator<<(std::ostream& out, const CaptureLogRecorder& log) -{ -    return log.streamto(out); -} -  #endif /* ! defined(LL_WRAPLLERRS_H) */ | 
