>
>
>
V510. The 'Foo' function receives class…


V510. The 'Foo' function receives class-type variable as Nth actual argument. This is unexpected behavior.

A variadic function (a function that uses an ellipsis as the last formal parameter) takes an object of a class type as an actual argument. The argument is a part of an ellipsis, which may indicate a logical error. Only POD types can serve as actual parameters for ellipsis.

POD stands for "Plain Old Data". Starting from C++11, POD types include:

  • Scalar types: arithmetic types (integral and floating-point), pointers, pointers to non-static data members or class functions, enumerations ('enum') or 'std::nullptr_t' (can be 'const' / 'volatile'- qualified);
  • Class types ('class', 'struct', or 'union') that meet the following requirements:
    • Copy/move constructors are trivial (generated by the compiler or noted as '= default');
    • Copy/move operators are trivial (generated by the compiler or noted as '= default');
    • Have a trivial non-deleted destructor;
    • The default constructor is trivial (generated by the compiler or marked as '= default');
    • All non-static data members have the same access control ('private', 'protected', or 'public');
    • Have no virtual functions or virtual base classes;
    • Have no non-static data members of the reference type;
    • All the non-static data members and base classes are themselves standard layout types;
    • Either have no base classes with non-static data members, or has no non-static data members in the most derived class and at most one base class with non-static data members;
    • Have no base classes of the same type as the first non-static data member.

If a non-POD type object is passed to an ellipsis of function as a parameter, it almost always indicates an error in a program. According to the C++11 standard:

Passing a potentially-evaluated argument of class type having a non-trivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

Here is an example of incorrect code:

void bar(size_t count, ...);

void foo()
{
  std::string s1 = ....;
  std::string s2 = ....;
  std::string s3 = ....;

  bar(3, s1, s2, s3);
}

Starting from C++11, you can use variadic templates to fix the error. They help store the information about the types of passed arguments:

template <typename T, typename ...Ts>
void bar(T &&arg, Ts &&...args);

void foo()
{
  std::string s1 = ....;
  std::string s2 = ....;
  std::string s3 = ....;

  bar(s1, s2, s3);
}

The analyzer will not generate a warning if the passing of a non-POD type object occurs in an unevaluated context (for example, within 'sizeof' / 'alignof'):

int bar(size_t count, ...);

void foo()
{
  auto res = sizeof(bar(2, std::string {}, std::string {}));
}

Practically, the diagnostic rule V510 helps detect the errors when passing arguments to formatted IO functions from C:

void foo(const std::wstring &ws)
{
  wchar_t buf[100];
  swprintf(buf, L"%s", ws);
}

Instead of a pointer to a string, the stack gets the contents of the object. This code will generate "abracadabra" in the buffer or cause a program crash.

Here's the correct version of the code:

wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws.c_str());

Instead of printf-like functions in C++, it is recommended to use safer alternatives. For example: 'boost::format', 'fmt::format', 'std::format' (C++20), etc.

Note. The diagnostic rule V510 also considers POD-type objects when passing them to the formatted IO function. Despite the fact that such a forwarding is safe, further work of the function with such arguments may lead to unexpected results.

If false positives from diagnostic rules cause inconvenience, you can suppress them within a specific function. To do that, insert a special type of comment into the code:

//-V:MyPrintf:510

Feature of using the CString class from the MFC library

We can see an error similar to the one above in the following code:

void foo()
{
  CString s;
  CString arg(L"OK");
  s.Format(L"Test CString: %s\n", arg);
}

The correct version of the code should look like this:

s.Format(L"Test CString: %s\n", arg.GetString());

Or, as MSDN suggests, to get a pointer to a string, you can use the explicit cast operator to 'LPCTSTR', implemented in the 'CString' class:

void foo()
{
  CString kindOfFruit = "bananas";
  int howmany = 25;
  printf("You have %d %s\n", howmany, (LPCTSTR)kindOfFruit);
}

However, 's.Format(L"Test CString: %s\n", arg);' is also correct, as are the others. More on this topic: "Big Brother helps you".

MFC developers implemented the 'CString' type in a special way so that it can be passed to functions like 'printf' and 'Format'. This is done quite cleverly. If you are interested, you can get acquainted with the implementation of the 'CStringT' class.

So, the analyzer makes an exception for the 'CString' type and considers the following code correct:

void foo()
{
  CString s;
  CString arg(L"OK");
  s.Format(L"Test CString: %s\n", arg);
}

This diagnostic is classified as:

You can look at examples of errors detected by the V510 diagnostic.