>
>
>
V575. Function receives suspicious argu…


V575. Function receives suspicious argument.

Анализатор обнаружил потенциально возможную ошибку, связанную с тем, что в качестве фактического аргумента в функцию передаётся очень странное значение.

Рассмотрим пример:

bool Matrix4::operator==(const Matrix4 &other) const
{
  if (memcmp(this, &other, sizeof(Matrix4) == 0))
    return true;
  ....
}

Здесь мы имеем дело с опечаткой. Круглая скобка поставлена не там, где нужно. К сожалению, это плохо заметно, и такая ошибка может очень долго присутствовать в коде. Из-за опечатки размер сравниваемой памяти вычисляется выражением sizeof(Matrix4) == 0. Так как результат выражение false, то сравнивается ноль байт памяти. Корректный вариант:

bool Matrix4::operator==(const Matrix4 &other) const
{
  if (memcmp(this, &other, sizeof(Matrix4)) == 0)
    return true;
  ....
}

Другой пример. Диагностическое правило определяет случаи, когда массив, состоящий из элементов enum, заполняется с помощью функции memset. При этом подразумевается, что размер элемента не равен одному байту. Такое заполнение будет некорректным, т.к. в этом случае значением заполнится каждый байт, а не каждый элемент массива.

Некорректный код:

enum E { V0, V1, V2, V3, V4 };
E array[123];
memset(array, V1, sizeof(array));

Если компилятор сделает размер каждого элемента равным, например, 4 байта, то все элементы массива будут равны значению 0x01010101, а вовсе не 0x00000001 (V1), как ожидает программист.

Корректный код для заполнения массива:

for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
  array[i] = V1;
}

Или:

std::fill(begin(array), end(array), V1);

Примечание. NULL - странный аргумент.

Иногда программисты пытаются вычислить, сколько требуется выделить памяти под буфер, используя код вот такого типа:

const char* format = getLocalizedString(id, resource);
int len = ::vsprintf(NULL, format, args);
char* buf = (char*) alloca(len);
::vsprintf(buf, format, args);

Учтите, что вызов ::vsprintf(NULL, format, args) некорректен. Вот что про это сказано в MSDN:

int vsprintf(*buffer, char *format, va_list argptr); 
....

vsprintf and vswprintf return the number of characters written, not including the terminating null character, or a negative value if an output error occurs. If buffer or format is a null pointer, these functions invoke the invalid parameter handler, as described in Parameter Validation. If execution is allowed to continue, these functions return -1 and set errno to EINVAL.

Дополнительная настройка

Есть несколько вариантов дополнительной настройки данного диагностического правила.

Для нулевых указателей

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

Примером может служить функция malloc. Эта функция может вернуть нулевой указатель. Соответственно, использование указателя, который вернула функция malloc, без предварительной проверки может привести к разыменованию нулевого указателя.

Иногда у наших пользователей возникает желание изменить поведение анализатора и заставить его считать, что, например, функция malloc не может вернуть нулевой указатель. Пользователь может использовать системные библиотеки, в которых ситуации нехватки памяти обрабатываются особым образом.

Также может возникнуть желание подсказать анализатору, что определённая функция может вернуть нулевой указатель.

В этом случае вы можете воспользоваться дополнительными настройками, которые описаны в разделе "Как указать анализатору, что функция может или не может возвращать nullptr".

Для передаваемого диапазона

Если функция принимает в качестве параметров границы некоторого диапазона, то можно разметить соответствующие параметры как верхнюю и нижнюю границу. Это даст анализатору дополнительную информацию, которая поможет найти нарушения предусловий функции (например, значение нижней границы больше, чем верхней).

Чтобы разметить параметры как нижнюю и верхнюю границы, необходимо воспользоваться механизмом пользовательских аннотаций в формате JSON и атрибутами lower_bound и upper_bound соответственно. Также, если есть параметр, значение которого проверяется на вхождение в диапазон, его нужно разметить как in_range_value (если такого параметра нет, то достаточно разметить только нижнюю и верхнюю границы).

Пример. Есть функция, семантически схожая с std::clamp, со следующей сигнатурой:

bool foo(int v, int lo, int hi);

Эта функция проверяет, что значение первого параметра лежит в диапазоне, переданном в качестве второго (нижняя граница) и третьего (верхняя граница) параметров.

Предположим, что есть следующий вызов данной функции:

if (foo(x, 100, 0))
{
  ....
}

Как мы видим, в вызове перепутаны местами аргументы для нижней и верхней границы. Тогда, чтобы получить срабатывание в месте вызова функции, необходимо разметить первый параметр функции foo как in_range_value, второй как lower_bound, а третий как upper_bound.

Исправленный код:

if (foo(x, 0, 100))
{
  ....
}

Примеры разметки параметров функций можно посмотреть здесь.

Примечание. Данная разметка необходима только для пользовательских функций.

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

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