Age | Commit message (Collapse) | Author |
|
`LLEventAPI` is specifically intended to allow a LEAP plugin, or a Lua script,
to access certain viewer functionality. Errors in external code like that
cannot be addressed during viewer development. Any code path that allows
external code in any form to crash the viewer opens up a potential abuse
vector, if a trusting user runs external code from an untrustworthy source.
`LLDispatchListener` reports exceptions back to its invoker, if the invoker
provides a "reply" `LLEventPump` name. Absent "reply", though,
`LLDispatchListener` is documented to let any such exception propagate. That
behavior may be okay for internal use, but in the case of the `LLEventAPI`
subclass, it veers into the abuse scenario described above.
Make `LLEventAPI` ensure that any exception propagating from `LLDispatchListener`
is caught and logged, but not propagated.
Also enrich error reporting for the "batch" `LLDispatchListener` operations.
|
|
LF, and trim trailing whitespaces as needed
|
|
that unconditionally return. This eliminates the problem of pacifying a
compiler that expects a return statement vs. a compiler that detects that
callFail() unconditionally throws.
Thanks, Ansariel.
|
|
(cherry picked from commit 2c1253c8ed2a1648317e6edd768b3fda00c56ce2)
|
|
(cherry picked from commit 3be250da90dd3d361df713056b881e017684e2b3)
|
|
for exceptions other than those thrown by base-class LLEventDispatcher.
Explain in LLDispatchListener Doxygen comments that for a request lacking a
"reply" key, any exception is allowed to propagate because it's likely to
reach the post() call that triggered the exception in the first place.
For batch LLDispatchListener operations, catch not only LLEventDispatcher::
DispatchError exceptions but any std::exception, so we can collect them to
report to the invoker. "Gotta catch 'em all!"
Make LLLeap catch any std::exception thrown by processing a request from the
plugin child process, log it and send a reply to the plugin. No plugin should
be allowed to crash the viewer.
(cherry picked from commit 94e10fd039b79f71ed8d7e10807b6e4eebd1928c)
|
|
Now an LLEventAPI subclass method can call callFail(...) to report an error,
and the error will be annotated with the leaf class name, the instance name
and the way the method was reached. The enriched error message will be logged
and either sent back to the invoker or propagated as an exception, depending
on the invocation tactic. In other words, a business method can use callFail()
to Do The Right Thing according to the LLEventDispatcher contract.
Introduce a nested SetState RAII class to set and clear transient state.
SetState's constructor accepts variadic stringize() arguments. The resulting
message is passed to LLEventDispatcher::setState(), which requires a SetState
reference because ONLY SetState should call setState(): state data really is
intended to be transient. SetState guarantees it will be cleared every time
it's set.
setState() respects previously-set transient state. If a call from an inner
function finds that transient state was already set by some ancestor, it
ignores the call and informs the caller by returning false. This lets a given
SetState instance recognize whether it is responsible for clearing the current
transient state.
operator<<(std::ostream&, const LLEventDispatcher&) now appends getState() to
the data reported by streaming *this. Non-static LLEventDispatcher::callFail()
already prepends *this to the reported error message.
Transient state is managed by a fiber_specific_ptr, since different threads
and even different fibers within a thread might be concurrently performing
different operations on the same LLEventDispatcher.
Introduce a back pointer to the parent LLEventDispatcher in DispatchEntry.
Populate it with a new constructor parameter, propagated through every
subclass constructor. Hoist ParamsDispatchEntry::callFail() up into its
DispatchEntry base class. Make it call non-static LLEventDispatcher::
callFail(), which prepends the reported error with instance and transient
state info. Use DispatchEntry::callFail() in LLSDDispatchEntry::call(),
instead of redundantly calling LLEventDispatcher::callFail().
Similarly, introduce an LLEventDispatcher back pointer into LLSDArgsMapper for
use by its own callFail() method.
The above should (!) eliminate the need to replicate LLEventDispatcher
instance info into every helper object's descriptive strings. In particular,
since the previous info was stored in each object by its constructor, it
couldn't report associated transient information about how the subject
callable was actually reached. Traversing a back pointer to the live
LLEventDispatcher instance gets us the most current state.
Make the internal three-argument LLEventDispatcher::try_call() method, which
implements each of the operator()() and public try_call() methods, use
SetState to append "[name]" (for explicit operator()(name, event) calls) or
"[key=name]" (for implicit operator()(event) calls) to streamed *this.
In the new LLDispatchListener request array and request map operations, use
SetState to indicate the current entry in the array or map, overriding the
lower-level state set by three-argument LLEventDispatcher::try_call().
(cherry picked from commit 2f8d7d20f43ab411ea0fe8b756cb696954acfb3e)
|
|
Add LL::always_return<T>(), which takes a callable and variadic arguments. It
calls the callable with those arguments and, if the returned type is
convertible to T, converts it and returns it. Otherwise it returns T().
always_return() is generalized from, and supersedes,
LLEventDispatcher::ReturnLLSD.
Add LL::function_arity<CALLABLE>, which extends
boost::function_types::function_arity by reporting results for both
std::function<CALLABLE> and boost::function<CALLABLE>. Use for
LL::apply(function, LLSD array) as well as for LLEventDispatcher.
Make LLEventDispatcher::add() overloads uniformly distinguish between a
callable (whether non-static member function or otherwise) that accepts a
single LLSD parameter, versus any other signature. Accepting exactly one LLSD
parameter signals that the callable will accept the composite arguments LLSD
blob, instead of asking LLEventDispatcher to unpack the arguments blob into
individual arguments.
Support add(subclass method) overloads for arbitrary-parameters methods as
well as for (const LLSD&) methods. Update tests accordingly: we need no longer
pass the boilerplate lambda instance getter that binds and returns 'this'.
Extract to the two LLEventDispatcher::make_invoker() overloads the LL::apply()
logic formerly found in ReturnLLSD.
Change lleventdispatcher_test.cpp tests from boost::bind(), which accepts
variadic arguments (even though it only passes a fixed set to the target
callable), to fixed-signature lambdas. This is because the revamped add()
overloads care about signature.
Add a test for a non-static method that accepts (const LLSD&), in other words
the composite arguments LLSD blob, and likewise returns LLSD.
(cherry picked from commit 95b787f7d7226ee9de79dfc9816f33c8bf199aad)
|
|
Now the value of the incoming event's dispatch key may be an LLSD::String (as
before), a map or an array, as documented in the augmented Doxygen class
comments. LLDispatchListener will attempt multiple calls before sending a
reply.
(cherry picked from commit 7671b37285c6cdf1afcddb0019311a822c8a4dc5)
|
|
This captures logic we intend to reuse for forthcoming LLDispatchListener
batched request support.
(cherry picked from commit 3cb6d374cb76e4b00dc121255e8f5aa4e777fa27)
|
|
Refine the special case of calling a nullary target function from an (event)
method, notably via LLDispatchListener.
(cherry picked from commit edcc52a9f60b1ec9b8f53603d6e2676558d41294)
|
|
Add a new LLEventDispatcher constructor accepting not only the map key to
extract a requested function name, but a second map key to extract the
arguments -- when required.
In Doxygen comments, clarify the difference between the two constructors.
Move interaction with the LLEventPump subsystem to LLDispatchListener.
LLEventDispatcher is intended to be directly called. On error, instead of
looking for a "reply" key in the invocation LLSD, throw DispatchError.
Publish DispatchError, formerly an implementation detail, and its new subclass
DispatchMissing.
Make both LLEventDispatcher::operator()() overloads return LLSD, leveraging
the new internal ReturnLLSD logic that returns a degenerate LLSD blob for a
void target callable and, for compatible types, converts the returned value to
LLSD. Notably, the public try_call() overloads still return bool; any value
returned by the target callable is discarded.
Clarify the operator() and try_call() argument requirements for target
callables registered to accept an LLSD array, in Doxygen comments and in code.
In particular, the 'event' passed to (event) overloads (vs. the (name, event)
overloads) must be an LLSD map, so it must contain an "args" key (or the new
arguments map key specified to the constructor) containing the LLSD args
array.
Since the use of the new args key depends on whether the target callable is
registered to accept an array or a map, pass it into DispatchEntry::call()
(and all subclass overrides), along with a bool to disambiguate whether we
reached that method from an LLEventDispatcher (event) invocation method or a
(name, event) invocation method.
Allow streaming an LLEventDispatcher instance to std::ostream, primarily to
facilitate construction of proper error messages.
Revert the 'name' argument of internal try_call(key, name, event) to
std::string. Ditch try_call_log(), try_call_one() and reply(). Fold
try_call_one() logic into three-argument try_call().
Refactor callFail() as a template method accepting both the exception to throw
and arbitrary stringize() arguments from which to construct the exception
message. Non-static callFail() implicitly prepends the instance and a colon to
the rest of the arguments, and calls static sCallFail(). The latter constructs
the exception message, logs it and throws the specified exception. This
obviates try_call_log().
Make implementation detail helper class LLSDArgsMapper a private member of
LLEventDispatcher so it can access sCallFail(): we now want all error handling
to go through that method. Add LLSDArgsMapper::callFail() resembling
LLSDEventDispatcher::callFail(), but without having to specify the exception:
only LLEventDispatcher will throw anything but generic DispatchError.
Give LLEventDispatcher::ParamsDispatchEntry and its subclasses
ArrayParamsDispatchEntry and MapParamsDispatchEntry a new 'name' argument to
identify error messages. Store it and use it implicitly in new callFail()
method, very like LLSDArgsMapper::callFail(). Make LLEventDispatcher::
addArrayParamsDispatchEntry() and addMapParamsDispatchEntry() pass a 'name'
that includes the LLEventDispatcher instance name as well as the name of the
specific registered callable. This way we need not intercept a low-level error
and annotate it with contextual data: we can just let the exception propagate.
Make ParamsDispatchEntry::call() override catch LL::apply_error thrown by an
invoker_function, and pass its message to callFail(), i.e. rethrow as
LLEventDispatcher::DispatchError.
Introduce ArrayParamsDispatchEntry::call() override for the special logic to
extract an arguments array from a passed LLSD map -- but only under the
circumstances described in the Doxygen comment.
Add similar logic to MapParamsDispatchEntry::call(), but with both argskey
itself and a value for argskey optional in the passed LLSD map.
Because LLEventDispatcher now has two constructor overloads, allow subclass
constructor LLDispatchListener() to accept zero or more trailing arguments.
This is different than giving LLDispatchListener's constructor a default final
argument, in that the subclass doesn't need to specify its default value:
that's up to the base-class constructor. But it does require that the subclass
constructor move to the header file.
Move private LLEventDispatcher::reply() method to LLDispatchListener. Extend
LLDispatchListener::process() to handle DispatchError by attempting to reply
with a map containing an "error" key, per convention. (In other words, move
that logic from LLEventDispatcher to LLDispatchListener.) Also, for a map LLSD
result, attempt to reply with that result; for other defined LLSD types,
attempt to reply with a map containing a "data" key. This is backwards
compatible with previous behavior because all previous LLDispatchListener
subclass methods returned void, which now produces an undefined LLSD blob,
which we don't bother trying to send in reply.
In lleventdispatcher_test.cpp, rework tut::lleventdispatcher_data::call_exc()
yet again to catch DispatchError instead of listening for an LLEventPump reply
event. Similarly, make call_logerr() catch DispatchError. Since the exception
should also be logged, we ignore it and focus on the log, as before.
Add tests <23> to <27>, exercising calls to new class DispatchResult methods
returning string, int, LLSD map, LLSD array and void.
(cherry picked from commit 2f9c915dd3d5137b5b2b1a57f0179e1f7a090f8c)
|
|
While calling a C++ function with arguments taken from a runtime-variable data
structure necessarily involves a bit of hocus-pocus, the best you can say for
the boost::fusion based implementation is that it worked. Sadly, template
recursion limited its applicability to a handful of function arguments. Now
that we have LL::apply(), use that instead. This implementation is much more
straightforward.
In particular, the LLSDArgsSource class, whose job was to dole out elements of
an LLSD array one at a time for the template recursion, goes away entirely.
Make virtual LLEventDispatcher::DispatchEntry::call() return LLSD instead of
void. All LLEventDispatcher target functions so far have been void; any
function that wants to respond to its invoker must do so explicitly by calling
sendReply() or constructing an LLEventAPI::Response instance. Supporting non-
void functions permits LLEventDispatcher to respond implicitly with the
returned value. Of course this requires a wrapper for void target functions
that returns LLSD::isUndefined().
Break out LLEventDispatcher::reply() from callFail(), so we can reply with
success as well as failure.
Make LLEventDispatcher::try_call_log() prepend the actual leaf class name and
description to any error returned by three-arg try_call(). That try_call()
overload reported "LLEventDispatcher(desc): " for a couple specific errors,
but no others. Hoist to try_call_log() to apply uniformly.
Introduce new try_call_one() method to diagnose name-not-found errors and
catch internal DispatchError and LL::apply_error exceptions. try_call_one()
returns a std::pair, containing either an error message or an LLSD value.
Make try_call_log() and three-arg try_call() accept LLSD 'name' instead of
plain std::string, allowing for the possibility of an array or map. That lets
us extend three-arg try_call() to break out new cases for the function selector
LLSD: isUndefined(), isArray(), isMap() and (current case) scalar String.
If try_call_one() reports an error, log it and try to send reply, as now. If
it returns LLSD::isUndefined(), e.g. from a void target function wrapper, do
nothing. But if it returns an LLSD map, try to send that back to the invoker.
And if it returns an LLSD scalar or array, wrap it in a map with key "data" to
respond to the invoker. Allowing a target function to return its result rather
than explicitly sending it opens the possibility of batched requests
(aggregate 'name') returning batched responses.
Almost every place that constructs LLEventDispatcher's internal DispatchError
exception called stringize() to format the what() string. Simplify calls by
making DispatchError accept variadic arguments and forward to stringize().
Add LL::invoke() to apply.h. Like LL::apply(), this is a (limited) C++14
foreshadowing of std::invoke(), with preprocessor conditionals to switch to
std::invoke() when that's available. Introduce LL::invoke() to handle a
callable that's actually a pointer to method.
Now our C++14 apply() implementation can accept pointer to method, using
invoke() to generalize the actual function call.
Also anticipate std::bind_front() with LL::bind_front(). For apply(func,
std::array) and our extensions apply(func, std::vector) and apply(func, LLSD),
we can't pass a pointer to method as the func unless the second argument
happens to be an array or vector of pointers (or references) to instances of
exactly the right class -- and of course LLSD can't store such at all. It's
tempting to pass std::bind(std::mem_fn(ptr_to_method), instance), but that
won't work: std::bind() requires a value or placeholder for each argument to
pass to the bound function. The bind() expression above would only work for a
nullary method. std::bind_front() would work, but that doesn't arrive until
C++20. Again, once we get there we'll defer to the std:: implementation.
Instead of the generic __cplusplus, check the appropriate feature-test macro
for availability of each of std::invoke(), std::apply() and std::bind_front().
Change apply() error handling from assert() to new LL::apply_error exception.
LLEventDispatcher must be able to intercept apply() errors. Move validation
and synthesis of the relevant error message to new apply.cpp source file.
Add to llptrto.h new LL::get_ref() and LL::get_ptr() template functions to
unify the cases of a calling template accepting either a pointer or a
reference. Wrapping the parameter in either get_ref() or get_ptr() allows
dereferencing the parameter as desired.
Move LL::apply(function, LLSD) argument validation/manipulation to a non-
template function in llsdutil.cpp: no need to replicate that logic in the
template for every CALLABLE specialization.
The trouble with passing bind_front(std::mem_fn(ptr_to_method), instance) to
apply() is that since bind_front() accepts and forwards variadic additional
arguments, apply() can't infer the arity of the bound ptr_to_method. Address
that by introducing apply_n<arity>(function, LLSD), permitting a caller to
infer the arity of ptr_to_method and explicitly pass it to apply_n().
Polish up lleventdispatcher_test.cpp accordingly. Wrong LLSD type and wrong
number of arguments now produce different (somewhat more informative) error
messages. Moreover, passing too many entries in an LLSD array used to work:
the extra arguments used to be ignored. Now we require that the size of the
array match the arity of the target function. Change the too-many-arguments
tests from success testing to error testing.
Replace 'foreach' aka BOOST_FOREACH macro invocations with range 'for'.
Replace STRINGIZE(item0 << item1 << ...) with stringize(item0, item1, ...).
(cherry picked from commit 9c049563b5480bb7e8ed87d9313822595b479c3b)
|
|
Instead of std::map<std::string, boost::shared_ptr>, use std::unique_ptr as
the mapped_type, using emplace() to store new entries. This more correctly
captures the desired semantics: we have no intention of passing around the
pointers in the map, we just want the map to delete them on destruction.
Use std::function instead of boost::function.
(cherry picked from commit 7ba53ef82db5683756e296225f0c8b838420a26e)
|
|
Bring over part of the LLEventDispatcher work inspired by DRTVWR-558.
|
|
It's a little distressing how often we have historically coded S32 or U32 to
pass a length or index.
There are more such assumptions in other viewer subdirectories, but this is a
start.
|
|
|
|
LazyEventAPI is a registrar that implicitly instantiates some particular
LLEventAPI subclass on demand: that is, when LLEventPumps::obtain() tries to
find an LLEventPump by the registered name.
This leverages the new LLEventPumps::registerPumpFactory() machinery. Fix
registerPumpFactory() to adapt the passed PumpFactory to accept TypeFactory
parameters (two of which it ignores). Supplement it with
unregisterPumpFactory() to support LazyEventAPI instances with lifespans
shorter than the process -- which may be mostly test programs, but still a
hole worth closing. Similarly, add unregisterTypeFactory().
A LazyEventAPI subclass takes over responsibility for specifying the
LLEventAPI's name, desc, field, plus whatever add() calls will be needed to
register the LLEventAPI's operations. This is so we can (later) enhance
LLLeapListener to consult LazyEventAPI instances for not-yet-instantiated
LLEventAPI metadata, as well as enumerating existing LLEventAPI instances.
The trickiest part of this is capturing calls to the various
LLEventDispatcher::add() overloads in such a way that, when the LLEventAPI
subclass is eventually instantiated, we can replay them in the new instance.
LLEventAPI acquires a new protected constructor specifically for use by a
subclass registered by a companion LazyEventAPI. It accepts a const reference
to LazyEventAPIParams, intended to be opaque to the LLEventAPI subclass; the
subclass must declare a constructor that accepts and forwards the parameter
block to the new LLEventAPI constructor. The implementation delegates to the
existing LLEventAPI constructor, plus it runs deferred add() calls.
LLDispatchListener now derives from LLEventStream instead of containing it as
a data member. The reason is that if LLEventPumps::obtain() implicitly
instantiates it, LLEventPumps's destructor will try to destroy it by deleting
the LLEventPump*. If the LLEventPump returned by the factory function is a
data member of an outer class, that won't work so well. But if
LLDispatchListener (and by implication, LLEventAPI and any subclass) is
derived from LLEventPump, then the virtual destructor will Do The Right Thing.
Change LLDispatchListener to *not* allow tweaking the LLEventPump name. Since
the overwhelming use case for LLDispatchListener is LLEventAPI, accepting but
silently renaming an LLEventAPI subclass would ensure nobody could reach it.
Change LLEventDispatcher's use of std::enable_if to control the set of add()
overloads available for the intended use cases. Apparently this formulation is
just as functional at the method declaration point, while avoiding the need to
restate the whole enable_if expression at the method definition point.
Add lazyeventapi_test.cpp to exercise.
|
|
Originally the LLEventAPI mechanism was primarily used for VITA testing. In
that case it was okay for the viewer to crash with LL_ERRS if the test script
passed a bad request.
With puppetry, hopefully new LEAP scripts will be written to engage
LLEventAPIs in all sorts of interesting ways. Change error handling from
LL_ERRS to LL_WARNS. Furthermore, if the incoming request contains a "reply"
key, send back an error response to the requester.
Update lleventdispatcher_test.cpp accordingly.
(cherry picked from commit de0539fcbe815ceec2041ecc9981e3adf59f2806)
|
|
|
|
|
|
An array-registered function has no param names, so you can only pass an
array: a map would be meaningless. Initial implementation of map-registered
functions assumed that since you CAN pass a map, you MUST pass a map. But in
fact it's meaningful to pass an array as well -- for whatever reason -- and
easy to implement, so there you are. Tests to follow.
|
|
On Windows, unlike on Mac or Linux, boost::ptr_map<> started insisting on this
concept of clonability. In other words, it wants to own a unique instance of
the pointee; if you copy a value_type -- even to dereference an iterator! --
it wants to construct a whole new instance of the mapped_type. That's nuts. A
std::map<..., boost::shared_ptr<>> has the property I want (the mapped_type
goes away when the entry is erased), plus it's willing to pass around the
shared_ptr to the same instance of the mapped_type. This change also permits
simplifying a couple awkward kludges I'd already had to make to accommodate
ptr_map's idiosyncracies.
|
|
The shortcut way to construct an LLSD array of size n is to assign LLSD() to
array[n-1]. That's fine -- as long as you remember not to do it for n == 0.
|
|
Until now, LLEventAPI has only been able to register functions specifically
accepting(const LLSD&). Typically you add a wrapper method to your LLEventAPI
subclass, register that, have it extract desired params from the incoming LLSD
and then call the actual function of interest.
With help from Alain, added new LLEventAPI::add() methods capable of
registering functions/methods with arbitrary parameter signatures. The code
uses boost::fusion magic to implicitly match incoming LLSD arguments to the
function's formal parameter list, bypassing the need for an explicit helper
method.
New add() methods caused an ambiguity with a previous convenience overload.
Removed that overload and fixed the one existing usage.
Replaced LLEventDispatcher::get() with try_call() -- it's no longer easy to
return a Callable for caller to call directly. But the one known use of that
feature simply used it to avoid fatal LL_ERRS on unknown function-name string,
hence the try_call() approach actually addresses that case more directly.
Added indra/common/lleventdispatcher_test.cpp to exercise new functionality.
|
|
|
|
for all registered operations. (untested)
Introduce LLEventDispatcher::getMetadata(name) query so you can discover, for
a given named operation, its query string and required parameters. (untested)
Introduce LLEventDispatcher::add() convenience methods allowing you to omit
description strings. Fix LLLoginInstance (which uses a non-LLEventAPI
LLEventDispatcher) back to description-less add() calls.
However, filter LLEventDispatcher::add() methods inherited by LLEventAPI so
that an LLEventAPI subclass *must* provide a description string.
|
|
with an event API. In addition to the LLEventPump name on which to listen,
LLEventAPI accepts a documentation string for event API introspection.
Give every LLEventDispatcher::add() overload a new documentation string
parameter for event API introspection.
Convert every existing event API to new conventions, introducing suitable
documentation strings for the API and each of its operations.
|
|
existing event calls to use state as "offline" or "online", with "change"
indicating the reason for this status event. Changed disconnect() to send
state "offline", change "disconnect" -- instead of replaying last auth
failure. Changed unit tests accordingly.
Changed LLLoginInstance::handleLoginEvent() to use LLEventDispatcher to route
calls to handleLoginFailure() et al.
Added LLEventDispatcher::get() to allow retrieving Callable by name and
testing for empty().
|
|
LLEventDispatcher and LLDispatchListener classes. See LLAresListener for
example usage.
|