Как получить предупреждение за забытое приведение в арифметике? - PullRequest
0 голосов
/ 19 января 2019

Рассмотрим эту ситуацию:

uint64_t add(uint32_t a, uint32_t b)
{
   return a + b; // programmer neglected (uint64_t) a + b.
}

Как мы можем заставить клиентский интерфейс C или C ++ GCC (или любого другого компилятора) предупреждать об этой ситуации: что операция выполняется в узком типе, который сразу же расширяется?

Я прочитал текущую документацию GCC и попробовал различные предупреждения, такие как -Wconversion, но ничего.

Ответы [ 3 ]

0 голосов
/ 19 января 2019

Поскольку код, с которым я работаю, компилируется как C или C ++, и все рассматриваемые типы - это typedef (которые легко перенаправляются на классы), мне приходит в голову, что решение C ++ возможно. Следующий пример кода намекает на идею:

#include <inttypes.h>

template <typename outer, typename inner, typename underlying> class arith {
public:
  underlying val;

  arith(underlying v) : val(v) { }
  explicit operator underlying () const { return val; }
  outer operator +(const inner &rhs) { return val + rhs.val; }
};

struct narrow;

struct narrow_result : public arith<narrow_result, narrow_result, uint32_t> {
  narrow_result(uint32_t v) : arith(v) { }
  narrow_result(const narrow &v);
};

struct narrow : public arith<narrow_result, narrow, uint32_t> {
  narrow(uint32_t v) : arith(v) { }
  narrow(const narrow_result &v) : arith(v.val) { }
};

inline narrow_result::narrow_result(const narrow &v)
: arith(v.val)
{
}

struct wide {
  uint64_t val;

  wide(uint64_t v) : val(v) { }
  wide(const narrow &v) : val(v) { }
  operator uint64_t () const { return val; }
  wide operator +(const wide &rhs) { return val + rhs.val; }
};

int main()
{
  narrow a = 42;
  narrow b = 9;
  wide c = wide(a) + b;
  wide d = a + b;          // line 43
  narrow e = a + b;
  wide f = a;              // line 45
  narrow g = a + b + b;    // line 46
  return 0;
}

Здесь GNU C ++ диагностирует только строку 43:

overflow.cc: In function ‘int main()’:
overflow.cc:43:16: error: conversion from ‘narrow_result’ to non-scalar type ‘wide’ requested

Обратите внимание, что неявное преобразование narrow в wide все еще разрешено, как видно в строке 45, просто потому, что wide имеет конструктор преобразования, нацеленный непосредственно на narrow. Ему просто не хватает одного для narrow_result.

Строка 46 показывает, что мы можем составлять арифметические операции. Это возможно, потому что narrow неявно преобразуется в narrow_result и и наоборот . Однако это неявное преобразование не включается в строку 45; narrow_result дополнения не конвертируется в narrow, так что тогда оно может конвертироваться в wide.

Все это может быть заключено в #ifdef __cplusplus и при наличии макроса условной отладки, того же макроса, который также позволяет альтернативные определения типов как typedefs для narrow и wide. Конечно, в базе шаблонов arith должны поддерживаться многочисленные другие арифметические операции.

0 голосов
/ 19 января 2019

Анализ кода Visual Studio может сделать это

Имеются различные проверки на целочисленное переполнение, включая операции без знака

  • C26450 RESULT_OF_ARITHMETIC_OPERATION_PROVABLY_LOSSY : [оператор] операция вызывает переполнение во время компиляции. Используйте более широкий тип для хранения операндов. Это предупреждение указывает на то, что во время компиляции арифметическая операция была доказуемо убыточной. Это можно утверждать, когда все операнды являются константами времени компиляции. В настоящее время мы проверяем операции левого сдвига, умножения, сложения и вычитания для таких переполнений.

    uint32_t multiply()
    {
       const uint32_t a = UINT_MAX; // the author used int here 
       const uint32_t b = 2;        // but I changed to unsigned for this question
       uint32_t c = a * b; // C26450 reported here [and also C4307]
       return c;
    }
    
  • C26451 RESULT_OF_ARITHMETIC_OPERATION_CAST_TO_LARGER_SIZE : использование оператора [operator] для байтового значения [size1] и последующее приведение результата к [ size2] значение байта. Приведите значение к более широкому типу перед вызовом оператора [operator] , чтобы избежать переполнения.

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

    void leftshift(int i) {
       unsigned long long x;
       x = i << 31; // C26451 reported here
            // code
    
    // Corrected source:
    void leftshift(int i) {
        unsigned long long x;
        x = (unsigned long long)i << 31; // OK
            // code
    }
    
  • C26454 RESULT_OF_ARITHMETIC_OPERATION_NEGATIVE_UNSIGNED : [operator] операция переносится за 0 и выдает большое число без знака во время компиляции

    Это предупреждение указывает, что операция вычитания приводит к отрицательному результату, который был оценен в неподписанном контексте. Это приводит к тому, что результат переносится за 0 и выдает действительно большое число без знака, что может привести к непреднамеренным переполнениям.

    // Example source:
    unsigned int negativeunsigned() {
        const unsigned int x = 1u - 2u; // C26454 reported here
        return x;
    }
    
    // Corrected source:
    unsigned int negativeunsigned() {
        const unsigned int x = 4294967295; // OK
        return x;
    }
    

Арифметические проверки переполнения в C ++ Core Check

Вот пример этого в действии

MSVC static code analysis result

Как видно из приведенных выше примеров, сам компилятор также может выдавать предупреждение, если операнды были константами времени компиляции. Если они были переменными, то вам нужен статический анализатор

Вы можете поиграть с этим в Compiler Explorer , хотя я не уверен, как заставить его действительно работать из командной строки. Если вы знаете, как передавать аргументы в анализ кода VS, пожалуйста, прокомментируйте ниже. В графическом интерфейсе MSVC просто нажмите Alt + F11

Для получения информации о том, как выполнить анализ, прочитайте Улучшения статического анализа C ++ для Visual Studio 2017 15.6 Preview 2


Clang не имеет для этого опции времени компиляции , но имеет возможность проверить во время выполнения

-fsanitize=unsigned-integer-overflow: переполнение целого числа без знака, при котором результат вычисления целого числа без знака не может быть представлен в его типе. В отличие от целочисленного переполнения со знаком, это не неопределенное поведение, но часто непреднамеренное. Это дезинфицирующее средство не проверяет неявные преобразования с потерями, выполненные перед таким вычислением (см. -fsanitize=implicit-conversion).

UndefinedBehaviorSanitizer

Он также может быть легко отключен

Отключение целочисленного переполнения без знака

Чтобы отключить отчеты от переполнения целых чисел без знака, вы можете установить UBSAN_OPTIONS=silence_unsigned_overflow=1. Эта функция, в сочетании с -fsanitize-recover=unsigned-integer-overflow, особенно полезна для подачи размытого сигнала без раздувания логов.

К сожалению, GCC поддерживает только -fsanitize=signed-integer-overflow. Там нет неподписанной версии

0 голосов
/ 19 января 2019

Мне не известен флаг GCC, который вызовет предупреждение. Статический анализатор Coverity выдаст предупреждение OVERFLOW_BEFORE_WIDEN , поскольку оно помечено в стандартах CERT.

Отказ от ответственности: я когда-то работал на Coverity.

...