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

Вариативная функция (функция, последним формальным параметром которой является эллипсис) принимает в качестве фактического аргумента, являющегося частью эллипсиса, объект классового типа, что может свидетельствовать о логической ошибке. В качестве фактического параметра для эллипсиса могут выступать только POD-типы.

POD – это аббревиатура от "Plain Old Data", что можно перевести как "Простые данные в стиле C". Начиная с C++11, к POD-типам относятся:

- Скалярные типы: арифметические типы (целые и вещественные), указатели, указатели на нестатические поля или функции класса, перечисления ('enum') или 'std::nullptr_t' (могут быть 'const' / 'volatile' квалифицированными);
- Классовый тип ('class', 'struct' или 'union'), который удовлетворяет нижеприведенным требованиям:
  - Конструкторы копирования/перемещения тривиальны (сгенерированы компилятором или помечены как '= default');
  - Операторы копирования/перемещения тривиальны (сгенерированы компилятором или помечены как '= default');
  - Имеет неудаленный тривиальный деструктор;
  - Конструктор по умолчанию тривиален (сгенерирован компилятором или помечен как '= default');
  - Все нестатические поля имеют одинаковый доступ ('private', 'protected' или 'public');
  - Не имеет виртуальных функций или виртуальных базовых классов;
  - Не имеет нестатических полей ссылочного типа;
  - Все нестатические поля и базовые классы имеют тип со стандартным размещением в памяти;
  - Не имеют базовых классов с нестатическими полями или не имеют полей в самом производном классе и не более одного базового класса с нестатическими полями;
  - Не имеют базовых классов того же типа, что имеет первое нестатическое поле.

Если эллипсису функции в качестве параметра передается объект не POD-типа, это практически всегда свидетельствует о наличии ошибки в программе. Согласно стандарту C++11:

> 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.

Пример кода с ошибкой:

    void bar(size_t count, ...);

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

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

Начиная с C++11, для исправления ошибки можно воспользоваться вариативными шаблонами, благодаря которым информация о типах переданных аргументов будет сохранена:

    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);
    }

Анализатор не будет выдавать предупреждение, если передача объекта не POD-типа происходит в невычисляемом контексте (например, внутри операторов 'sizeof' / 'alignof'):

    int bar(size_t count, ...);

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

На практике диагностическое правило V510 помогает выявлять ошибки при передаче аргументов в функции форматного ввода/вывода из C:

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

Вместо указателя на строку в стек попадает содержимое объекта. Такой код приведет к формированию в буфере "абракадабры" или к аварийному завершению программы.

Корректный вариант кода должен выглядеть так:

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

Вместо printf-подобных функций в C++ рекомендуется использовать более безопасные аналоги. Например, 'boost::format', 'fmt::format', 'std::format' (C++20) и т.п.

Примечание. Диагностическое правило V510 также рассматривает объекты POD-типов при их передаче в функции форматного ввода/вывода. Несмотря на то, что такая передача безопасна, дальнейшая работа функции с такими аргументами может привести к непредвиденным результатам.

Если ложные срабатывания диагностического правила доставляют неудобства, их можно подавить на конкретной функции, вставив в код комментарий специального вида:

    //-V:MyPrintf:510

## Особенность при использовании класса CString из библиотеки MFC

Ошибку, аналогичную приведенной выше, мы должны наблюдать и в следующем коде:

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

Корректный вариант кода должен выглядеть так:

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

Или, как предлагается в MSDN, для получения указателя на строку можно использовать оператор явного приведения к 'LPCTSTR', реализованный в классе 'CString':

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

Однако первый вариант 's.Format(L"Test CString: %s\n", arg);' также является корректным, как и остальные. Подробнее эта тема обсуждается в статье "Большой брат помогает тебе".

Разработчики MFC реализовали тип 'CString' специальным образом, чтобы его можно было передавать в функции вида 'printf' и 'Format'. Сделано это достаточно хитро, и те, кто интересуется, могут ознакомиться с реализацией класса 'CStringT'.

Таким образом, анализатор делает исключение для типа 'CString' и считает следующий код корректным:

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

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки использования форматной строки.

Данная диагностика классифицируется как:

CWE-686 CERT-FIO47-C

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V510.