summaryrefslogtreecommitdiff
path: root/indra/llcommon/llsdutil.h
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2023-10-29 11:56:17 -0400
committerNat Goodspeed <nat@lindenlab.com>2023-10-29 11:56:17 -0400
commitf7d2d40b3057f5bc249c88784b35443aad8de7aa (patch)
tree1e5a228889616e1130e0546ca432fa3096577f9a /indra/llcommon/llsdutil.h
parent2667653d41d3b4799bf319783a884cbac7f826da (diff)
DRTVWR-587: Fix LL::apply(function, LLSD array).
We define a specialization of LLSDParam<const char*> to support passing an LLSD object to a const char* function parameter. Needless to remark, passing object.asString().c_str() would be Bad: destroying the temporary std::string returned by asString() would immediately invalidate the pointer returned by its c_str(). But when you pass LLSDParam<const char*>(object) as the parameter, that specialization itself stores the std::string so the c_str() pointer remains valid as long as the LLSDParam object does. Then there's LLSDParam<LLSD>, used when we don't have the parameter type available to select the LLSDParam specialization. LLSDParam<LLSD> defines a templated conversion operator T() that constructs an LLSDParam<T> to provide the actual parameter value. So far, so good. The trouble was with the implementation of LLSDParam<LLSD>: it constructed a _temporary_ LLSDParam<T>, implicitly called its operator T() and immediately destroyed it. Destroying LLSDParam<const char*> destroyed its stored string, thus invalidating the c_str() pointer before the target function was entered. Instead, make LLSDParam<LLSD>::operator T() capture each LLSDParam<T> it constructs, extending its lifespan to the lifespan of the LLSDParam<LLSD> instance. For this, derive each LLSDParam specialization from LLSDParamBase, a trivial base class that simply establishes the virtual destructor. We can then capture any specialization as a pointer to LLSDParamBase. Also restore LazyEventAPI tests on Mac.
Diffstat (limited to 'indra/llcommon/llsdutil.h')
-rw-r--r--indra/llcommon/llsdutil.h75
1 files changed, 55 insertions, 20 deletions
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index cd68f938ea..ad54d1b0be 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -34,7 +34,9 @@
#include "llsd.h"
#include <boost/functional/hash.hpp>
#include <cassert>
+#include <memory> // std::shared_ptr
#include <type_traits>
+#include <vector>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@@ -302,6 +304,11 @@ LLSD map(Ts&&... vs)
/*****************************************************************************
* LLSDParam
*****************************************************************************/
+struct LLSDParamBase
+{
+ virtual ~LLSDParamBase() {}
+};
+
/**
* LLSDParam is a customization point for passing LLSD values to function
* parameters of more or less arbitrary type. LLSD provides a small set of
@@ -319,7 +326,7 @@ LLSD map(Ts&&... vs)
* @endcode
*/
template <typename T>
-class LLSDParam
+class LLSDParam: public LLSDParamBase
{
public:
/**
@@ -327,13 +334,13 @@ public:
* value for later retrieval
*/
LLSDParam(const LLSD& value):
- _value(value)
+ value_(value)
{}
- operator T() const { return _value; }
+ operator T() const { return value_; }
private:
- T _value;
+ T value_;
};
/**
@@ -343,10 +350,30 @@ private:
* specialization.
*/
template <>
-class LLSDParam<LLSD>
+class LLSDParam<LLSD>: public LLSDParamBase
{
private:
LLSD value_;
+ // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
+ // demand. Returning that engages LLSDParam<T>::operator T(), producing
+ // the desired result. But LLSDParam<const char*> owns a std::string whose
+ // c_str() is returned by its operator const char*(). If we return a temp
+ // LLSDParam<const char*>, the compiler can destroy it right away, as soon
+ // as we've called operator const char*(). That's a problem! That
+ // invalidates the const char* we've just passed to the subject function.
+ // This LLSDParam<LLSD> is presumably guaranteed to survive until the
+ // subject function has returned, so we must ensure that any constructed
+ // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
+ // each LLSDParam<T> on the heap and capturing a smart pointer in a vector
+ // works. We would have liked to use std::unique_ptr, but vector entries
+ // must be copyable.
+ // (Alternatively we could assume that every instance of LLSDParam<LLSD>
+ // will be asked for at most ONE conversion. We could store a scalar
+ // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
+ // the unique_ptr is empty. But some future change in usage patterns, and
+ // consequent failure of that assertion, would be very mysterious. Instead
+ // of explaining how to fix it, just fix it now.)
+ mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
public:
LLSDParam(const LLSD& value): value_(value) {}
@@ -358,7 +385,15 @@ public:
/// otherwise, instantiate a more specific LLSDParam<T> to convert; that
/// preserves the existing customization mechanism
template <typename T>
- operator T() const { return LLSDParam<std::decay_t<T>>(value_); }
+ operator T() const
+ {
+ // capture 'ptr' with the specific subclass type because converters_
+ // only stores LLSDParamBase pointers
+ auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
+ // keep the new converter alive until we ourselves are destroyed
+ converters_.push_back(ptr);
+ return *ptr;
+ }
};
/**
@@ -375,17 +410,17 @@ public:
*/
#define LLSDParam_for(T, AS) \
template <> \
-class LLSDParam<T> \
+class LLSDParam<T>: public LLSDParamBase \
{ \
public: \
LLSDParam(const LLSD& value): \
- _value((T)value.AS()) \
+ value_((T)value.AS()) \
{} \
\
- operator T() const { return _value; } \
+ operator T() const { return value_; } \
\
private: \
- T _value; \
+ T value_; \
}
LLSDParam_for(float, asReal);
@@ -401,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary);
* safely pass an LLSDParam<const char*>(yourLLSD).
*/
template <>
-class LLSDParam<const char*>
+class LLSDParam<const char*>: public LLSDParamBase
{
private:
// The difference here is that we store a std::string rather than a const
// char*. It's important that the LLSDParam object own the std::string.
- std::string _value;
+ std::string value_;
// We don't bother storing the incoming LLSD object, but we do have to
- // distinguish whether _value is an empty string because the LLSD object
+ // distinguish whether value_ is an empty string because the LLSD object
// contains an empty string or because it's isUndefined().
- bool _undefined;
+ bool undefined_;
public:
LLSDParam(const LLSD& value):
- _value(value),
- _undefined(value.isUndefined())
+ value_(value),
+ undefined_(value.isUndefined())
{}
- // The const char* we retrieve is for storage owned by our _value member.
+ // The const char* we retrieve is for storage owned by our value_ member.
// That's how we guarantee that the const char* is valid for the lifetime
// of this LLSDParam object. Constructing your LLSDParam in the argument
// list should ensure that the LLSDParam object will persist for the
// duration of the function call.
operator const char*() const
{
- if (_undefined)
+ if (undefined_)
{
// By default, an isUndefined() LLSD object's asString() method
// will produce an empty string. But for a function accepting
@@ -435,7 +470,7 @@ public:
// case, though, no LLSD value could pass NULL.
return NULL;
}
- return _value.c_str();
+ return value_.c_str();
}
};
@@ -592,7 +627,7 @@ namespace LL
* apply(function, LLSD array)
*****************************************************************************/
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
-// apply_impl()
+// the function of interest
LLSD apply_llsd_fix(size_t arity, const LLSD& args);
// Derived from https://stackoverflow.com/a/20441189