V510. The 'Foo' function is not expected to receive class-type variable as 'N' actual argument.


Существуют функции, в описании которых невозможно указать число и типы всех допустимых параметров. Тогда список формальных параметров завершается эллипсисом (...), что означает: "и, возможно, еще несколько аргументов". Пример эллипсис функции: "int printf(const char* ...);". В качестве фактического параметра для эллипсиса могут выступать только POD типы.

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

  • все встроенные арифметические типы (включая wchar_t и bool);
  • типы, объявленные с помощью ключевого слова enum;
  • указатели;
  • POD-структуры (struct или class) и POD-объединения (union), которые удовлетворяют нижеприведенным требованиям:
    • не содержат пользовательских конструкторов, деструктора или копирующего оператора присваивания;
    • не имеют базовых классов;
    • не содержат виртуальных функций;
    • не содержат защищенных (protected) или закрытых (private) нестатических членов данных;
    • не содержат нестатических членов данных не-POD-типов (или массивов из таких типов), а также ссылок.

Если эллипсис функции в качестве параметра передается объект класса, то практически всегда свидетельствует о наличии ошибки в программе. На практике правило V510 помогает выявить ошибочный код следующего вида:

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

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

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

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

Из-за того, что в функции с переменным количеством аргументов можно передать все что угодно их и не рекомендуют использовать практически во всех книгах по программированию на языке Си++. Вместо этого предлагается использовать безопасные механизмы, например, boost::format.

Примечание касательно C++11

Новый стандарт говорит: C++11 5.2.2/7: Passing a potentially-evaluated argument of class type having a non-trivial copy constructor, a non-trivial move contructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

Таким образом, можно передавать в эллипсис функции "более разнообразные" объекты. Однако мы не стали ничего менять в диагностическом правиле. В 99% передача сложного класса в качестве аргумента является опечаткой или иной ошибкой. Такой код стоит проверить. Если ложные срабатывания доставляют неудобства, можно разметить функции специальным образом. Пример:

//-V:MySuperPrint:510

Подробнее о массовом подавлении предупреждений можно прочитать в разделе "Подавление ложных предупреждений".

Отметим особенность, связанную с использованием класса CString из библиотеки MFC

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

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

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

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

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

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

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

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

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

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

Дополнительные материалы

Согласно Common Weakness Enumeration, потенциальные ошибки, найденные с помощью этой диагностики, классифицируются как CWE-686.

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


Найденные ошибки

Проверено проектов
354
Собрано ошибок
13 290

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;