# 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))
    {
      ....
    }

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

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

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки разыменования нулевого указателя/нулевой ссылки [* см. примечание касательно языков C#, Java], Ошибки переполнения буфера (записи или чтения за пределами выделенной для буфера памяти), Ошибки управления динамической памятью (выделения, освобождения, использования освобожденной памяти) [* см. примечание касательно языков C#, Java], Ошибки использования неинициализированных переменных, Ошибки непроверенного использования чувствительных данных (ввода пользователя, файлов, сети и пр.).

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

CWE-1164 CWE-628 CERT-EXP37-C

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