Jump to content
  • Advertisement
Sign in to follow this  
Juliean

C++ MSVC - Function argument evaluation order (bug?)

This topic is 449 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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!

Share this post


Link to post
Share on other sites
Advertisement
17 minutes ago, Juliean said:

Is there any clause in the C++-standard that allows for argument-evaluation to happen out-of-order under certain circumstances

Most definitely.  In fact all of Annex C of the ISO/IEC 9899 C language standard deals with clarifying exactly where the sequence points in evaluating expressions are.  Section [6.5.2.2][10] also explicitly states the following.

Quote

10 The order of evaluation of the function designator, the actual arguments, and
subexpressions within the actual arguments is unspecified, but there is a sequence point
before the actual call.

Section [5.1.2.3] clearly specifies that the value of objects between sequence points may not be relied on. Paragraph 2 of that section defines what a sequence point is, and the rest of the section goes to great lengths in standardese to describe what a sequence point is not.

Quote

2 Accessing a volatile object, modifying an object, modifying a file, or calling a function
that does any of those operations are all side effects, 11) which are changes in the state of
the execution environment. Evaluation of an expression may produce side effects. At
certain specified points in the execution sequence called sequence points, all side effects
of previous evaluations shall be complete and no side effects of subsequent evaluations
shall have taken place. (A summary of the sequence points is given in annex C.)

Section [1.2] of the ISO C++ standard explicitly includes the C language standard as a normative reference (that is to say, it extends the C language standard).

In other words, any program that relies on the order of evaluation of arguments to a function is malformed, because anything between the start of the function call and the actual execution of the function itself lacks a sequence point and the compiler could emit code to evaluate the arguments in any order, at any time, for any number of reasons.  It doesn't matter what calling conventions you're using, because that affects only the way data are passed and received -- which only happens after arguments are evaluated and their side effects complete.

You can not rely on the order in which arguments are evaluated in C or C++.  Ever.  Ever ever. 

Share this post


Link to post
Share on other sites

Thanks, makes a lot more sense now. I somehow mistake the right-to-left argument passing to evaluation order, oh well. Still strange that this problem only shows up now and in such a specific case, but oh well, thats what you get when you rely on unspecified behaviour.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!