Hi,
So yesterday I had this really weird bug, which fortunately I managed to work around, but I'd still like to know whats going on in the first place.
So here's the deal: For my script-systems C++-function-bindings, I (variadic) templates like this:
template<typename Return, typename Class, typename... Args>
using Function = Return (Class::*)(Args...);
template<typename Return, CheckHasReturn<Return> = 0>
static void Call(size_t index, Class& object, Function func, CallState& state)
{
auto value = (object.*func)(getAttribute<Args>(index, state)...);
returnValue<Return>(0, state, value);
}
This allows for me to call a function-pointer from a bunch of arguments stored continuously in memory. In case you aren't familiar with variadic templates (or didn't know this was possible): getAttribute<Args>()... inserts the getAttribute-function for each individual template argument, like that:
void test(int, float)
{
}
Call(&test) =>
index = 1;
func(getAttribute<int>(index), getAttribute<float>(index));
Now here's the important part: Since I'm using the (default) cdecl/thiscall calling order, attributes are evaluted right-to-left, so index starts at the maximum value and is decremented in getAttribute().
And now to the bug:
Inside my plugin-projects, getAttribute would sometimes get called out of order. An example of such a function would be:
void ShowOptions(event::CallState& state, const std::vector<core::LocalizedString*>& vOptions, int selection, bool allowCancel, bool playSounds);
Now here's what happened: Instead of calling getAttribute<bool>(3), getAttribute<bool>(2), ... it was calling getAttribute<const std::vector<core::LocalizedString*>&>(3) first, which resulted in an misinterpretation of the last "bool" argument as a vector-type (it was still being inserted into the correct argument-slot, kind of obvious since there likely would've been a compile-error otherwise). After that, it would resume to calling the leftover getAttribute-functions in the "correct" order (in reality though, this resulted in an early crash).
Now my question is: How can this happen? Is there any clause in the C++-standard that allows for argument-evaluation to happen out-of-order under certain circumstances, maybe just in conjunction with the variadic-function unpacking "trick" I've used here? Or could it be a compiler-bug (in which case I'd be tempted to smack MSVC finally; that thing is just getting messier and messier)? You'd probably need to know what else I tried to pin down the exact problem in order to know for sure, so here:
- Since it only happens inside the plugin-projects (loaded via DLL), I've made sure that all compiler-settings match.
- I've also found out that under no circumstances, neigther in the plugin projects nor the main engine codebase, any such bug could be directly reproduced. As in, I've tried to reduce the involved code for the function-binding (which is quite a lot) to a minimal working example, but all attempts failed to yield a similar result - all the arguments/functions were evaluted/called in the required order.
- What I've been able to do is pinpoint the problem to (appearently) the getAttribute<>-function. Long story short, I'm supporting const vector<>& and similar constructs via an specialized templated class inside getAttribute:
namespace detail
{
template<typename Arg>
struct AttributeHelper
{
static Arg Call(size_t& index, CallState& state)
{
return state.GetAttribute<core::CleanType<Arg>>(index--);
}
};
template<typename Arg>
struct AttributeHelper<const std::vector<Arg>&>
{
static std::vector<Arg> Call(size_t& index, CallState& state)
{
return state.GetAttribute<std::vector<Arg>>(index--);
}
};
}
template<typename Arg>
decltype(auto) getAttribute(size_t& index, CallState& state)
{
return detail::AttributeHelper<Arg>::Call(index, state);
}
The actual code is a lot more complicated, but as I've seen, the "bug" is triggered when one of the AttributeHelper specializations are being choosen. Now again, I couldn't reduce this problem to a more simplified version of the code, but it seems thats the root of the problem here.
I've actually been able to workaround the issue by using an std::index_sequence instead of manually counting the index (which imposes a few additional limiations, but oh well). The issue still remains though, and I still don't have any damn clue whats exactly going on or why.
I know this is probably a deeply complicated technical issue, and I don't require immediate help, but I'd still like to know if the experienced behaviour (out-of-order evaluation of function arguments under specific circumstances) is actually valid or just another microsoft-related bug. Any ideas?
Thanks!
↧