Каким-то образом нам довелось проверить большинство библиотек, составляющих коллекцию под названием «Awesome hpp». Это небольшие заголовочные проекты на C++. Надеемся, информация о найденных ошибках поможет сделать библиотеки лучше. Мы также будем рады видеть, что разработчики используют PVS-Studio на регулярной основе благодаря предоставляемой нами опции бесплатной лицензии.

Далее следует обзор ошибок, обнаруженных в различных библиотеках из тщательно отобранного списка замечательных библиотек C++, состоящих только из заголовков: awesome-hpp.

Об этом списке я узнал из подкаста Кроссплатформенная мобильная телефония. Пока мы на этом, я рекомендую всем программистам C++ проверить CppCast. Это первый подкаст для разработчиков C++ от разработчиков C++!

Несмотря на большое количество проектов, составляющих список, багов было немного. Тому есть три причины:

  • Проекты крошечные. Многие буквально состоят только из заголовочного файла.
  • Мы решили пропустить некоторые проекты, так как их было слишком сложно компилировать.
  • Часто вы не можете выяснить, есть ли в шаблонных классах/функциях ошибки, пока не создадите их экземпляры. Следовательно, многие ошибки можно обнаружить только в реальном проекте, активно использующем библиотеку. Что касается нашего метода анализа, то мы просто включили заголовки в пустой .cpp файл и запустили проверку, что резко ограничило ее эффективность.

Тем не менее, мы собрали достаточно предупреждений, чтобы написать статью, которую вы сейчас читаете, и пару дополнительных.

Примечание для моих товарищей по команде :). Когда я что-то делаю, мне нравится ставить и достигать сразу несколько целей, и я призываю вас последовать моему примеру. Узнав о коллекции awesome-hpp, мне удалось выполнить следующие полезные задачи:

Примечание для разработчиков библиотек. Вы можете использовать PVS-Studio для бесплатной проверки проектов с открытым исходным кодом. Чтобы получить бесплатную лицензию на использование с вашим проектом с открытым исходным кодом, пожалуйста, заполните эту форму.

Хорошо, давайте перейдем к нашему обзору.

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

лучший

Краткое описание библиотеки iutest:

iutest — это платформа для написания тестов C++.

template<typename Event>
pool_handler<Event> & assure() {
  ....
  return static_cast<pool_handler<Event> &>(it == pools.cend() ?
    *pools.emplace_back(new pool_handler<Event>{}) : **it);
  ....
}

Диагностическое сообщение PVS-Studio: V1023 В контейнер pools методом emplace_back добавлен указатель без владельца. Утечка памяти произойдет в случае исключения. entt.hpp 17114

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

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

Фиксированный код:

pools.emplace_back(std::make_unique<pool_handler<Event>>{})

Еще одно проблемное место: V1023 В контейнер pools методом emplace_back добавляется указатель без владельца. Утечка памяти произойдет в случае исключения. entt.hpp 17407

jsoncons

Краткое описание библиотеки jsoncons:

Библиотека C++, содержащая только заголовки, для создания форматов данных, подобных JSON и JSON, с указателем JSON, исправлением JSON, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON.

Ошибка 1

static constexpr uint64_t basic_type_bits = sizeof(uint64_t) * 8;
uint64_t* data() 
{
  return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_;
}
basic_bigint& operator<<=( uint64_t k )
{
  size_type q = (size_type)(k / basic_type_bits);
  ....
  if ( k )  // 0 < k < basic_type_bits:
  {
    uint64_t k1 = basic_type_bits - k;
    uint64_t mask = (1 << k) - 1;             // <=
    ....
    data()[i] |= (data()[i-1] >> k1) & mask;
    ....
  }
  reduce();
  return *this;
}

Диагностическое сообщение PVS-Studio: V629 Попробуйте проверить выражение 1 ‹‹ k. Битовый сдвиг 32-битного значения с последующим расширением до 64-битного типа. bigint.hpp 744

Этот баг уже подробно обсуждался в статье «Почему важно применять статический анализ для открытых библиотек, которые вы добавляете в свой проект». В двух словах, чтобы получить правильные значения маски, вы должны написать следующее:

uint64_t mask = (static_cast<uint64_t>(1) << k) - 1;

Вот альтернативная версия:

uint64_t mask = (1ull << k) - 1;

Еще одна похожая ошибка была обнаружена здесь: V629. Рассмотрим проверку выражения 1 ‹‹ k. Битовый сдвиг 32-битного значения с последующим расширением до 64-битного типа. bigint.hpp 779

Ошибка 2

template <class CharT = typename std::iterator_traits<Iterator>::value_type>
typename std::enable_if<sizeof(CharT) == sizeof(uint16_t)>::type 
next() UNICONS_NOEXCEPT
{
    begin_ += length_;
    if (begin_ != last_)
    {
        if (begin_ != last_)
        {
  ....
}

Диагностическое сообщение PVS-Studio: V571 Периодическая проверка. Условие if (begin_ != last_) уже проверено в строке 1138. unicode_traits.hpp 1140

Это странная двойная проверка. Я подозреваю, что второе условие содержит опечатку и предназначалось для проверки какого-то другого значения.

зажим

Краткое описание библиотеки clipp:

clipp — интерфейсы командной строки для современного C++. Простая в использовании, мощная и выразительная обработка аргументов командной строки для C++ 11/14/17, содержащаяся в одном заголовочном файле.

inline bool
fwd_to_unsigned_int(const char*& s)
{
  if(!s) return false;
  for(; std::isspace(*s); ++s);
  if(!s[0] || s[0] == '-') return false;
  if(s[0] == '-') return false;
  return true;
}

Диагностическое сообщение PVS-Studio: V547 Выражение ‘s[0] == ‘-’’ всегда ложно. clipp.h 303

На самом деле это не ошибка — просто избыточный код. Элемент дважды проверяется на наличие знака минус.

SimpleIni

Краткое описание библиотеки SimpleIni:

Кроссплатформенная библиотека, предоставляющая простой API для чтения и записи файлов конфигурации в стиле INI. Он поддерживает файлы данных в форматах ASCII, MBCS и Unicode.

#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux))

Диагностическое сообщение PVS-Studio: V1040 Возможна опечатка в написании предопределенного имени макроса. Макрос _linux похож на __linux. SimpleIni.h 2923

Кажется, в имени макроса _linux отсутствует символ подчеркивания: __linux. В любом случае, этот макрос устарел в POSIX, поэтому вместо него следует использовать __linux__.

Парсер CSV

Краткое описание библиотеки CSV Parser:

Современная библиотека C++ для чтения, записи и анализа файлов CSV (и аналогичных файлов).

CSV_INLINE void CSVReader::read_csv(const size_t& bytes) {
  const size_t BUFFER_UPPER_LIMIT = std::min(bytes, (size_t)1000000);
  std::unique_ptr<char[]> buffer(new char[BUFFER_UPPER_LIMIT]);
  auto * HEDLEY_RESTRICT line_buffer = buffer.get();
  line_buffer[0] = '\0';
  ....
  this->feed_state->feed_buffer.push_back(
    std::make_pair<>(std::move(buffer), line_buffer - buffer.get())); // <=
  ....
}

Диагностическое сообщение PVS-Studio: V769 Указатель buffer.get() в выражении line_buffer — buffer.get() равен nullptr. Полученное значение бессмысленно и не должно использоваться. csv.hpp 4957

Случай интересный, требующий тщательного расследования, поэтому я решил написать о нем небольшой отдельный пост. К тому же, экспериментируя с подобным кодом, я обнаружил недоработку в коде самого PVS-Studio :). Из-за этого недостатка анализатор иногда молчит, когда должен выдать предупреждение.

Короче говоря, будет ли работать этот код, зависит от порядка вычисления аргументов, а это зависит от компилятора.

PПечать

Краткое описание библиотеки PPrint:

Красивый принтер для современного C++.

template <typename Container>
typename std::enable_if<......>::type print_internal(......) {
  ....
  for (size_t i = 1; i < value.size() - 1; i++) {
    print_internal(value[i], indent + indent_, "", level + 1);
    if (is_container<T>::value == false)
      print_internal_without_quotes(", ", 0, "\n");
    else
      print_internal_without_quotes(", ", 0, "\n");
  }
  ....
}

Диагностическое сообщение PVS-Studio: V523 Оператор then эквивалентен оператору else. pprint.hpp 715

Очень странно, что одна и та же логика выполняется независимо от результата проверки. Уточняющий комментарий также не приводится. Это очень похоже на ошибку копирования-вставки.

Похожие предупреждения:

  • V523 Оператор "then" эквивалентен оператору "else". pprint.hpp 780
  • V523 Оператор "then" эквивалентен оператору "else". pprint.hpp 851
  • V523 Оператор "then" эквивалентен оператору "else". pprint.hpp 927
  • V523 Оператор "then" эквивалентен оператору "else". pprint.hpp 1012

Стрф

Краткое описание библиотеки Strf:

Быстрая библиотека форматирования C++, поддерживающая преобразование кодировки.

Ошибка 1

template <int Base>
class numpunct: private strf::digits_grouping
{
  ....
  constexpr STRF_HD numpunct& operator=(const numpunct& other) noexcept
  {
    strf::digits_grouping::operator=(other);
    decimal_point_ = other.decimal_point_;
    thousands_sep_ = other.thousands_sep_;
  }
  ....
};

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. numpunct.hpp 402

«Вернуть *это;» заявление отсутствует в конце.

Ошибка 2 (того же характера)

template <int Base>
class no_grouping final
{
  constexpr STRF_HD no_grouping& operator=(const no_grouping& other) noexcept
  {
    decimal_point_ = other.decimal_point_;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. numpunct.hpp 528.

Индикаторы

Краткое описание библиотеки Индикаторы:

Индикаторы активности для современного C++.

static inline void move_up(int lines) { move(0, -lines); }
static inline void move_down(int lines) { move(0, -lines); }   // <=
static inline void move_right(int cols) { move(cols, 0); }
static inline void move_left(int cols) { move(-cols, 0); }

Диагностическое сообщение PVS-Studio: V524 Странно, что тело функции move_down полностью эквивалентно телу функции move_up. индикаторы.hpp 983

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

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

Маниф

Краткое описание библиотеки manif:

manif — это библиотека теории лжи C++11, состоящая только из заголовков, для оценки состояния, предназначенная для приложений робототехники.

template <typename _Derived>
typename LieGroupBase<_Derived>::Scalar*
LieGroupBase<_Derived>::data()
{
  return derived().coeffs().data();
}
template <typename _Derived>
const typename LieGroupBase<_Derived>::Scalar*
LieGroupBase<_Derived>::data() const
{
  derived().coeffs().data(); // <=
}

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. ложь_группа_база.h 347

Неконстантная функция реализована правильно, а константная — нет. Интересно, как так получилось…

Притворяться

Краткое описание библиотеки FakeIt:

FakeIt – это простая фиктивная платформа для C++. Он поддерживает GCC, Clang и MS Visual C++. FakeIt написан на C++11 и может использоваться для тестирования проектов C++11 и C++.

template<typename ... arglist>
struct ArgumentsMatcherInvocationMatcher :
         public ActualInvocation<arglist...>::Matcher {
  ....
  template<typename A>
  void operator()(int index, A &actualArg) {
      TypedMatcher<typename naked_type<A>::type> *matcher =
        dynamic_cast<TypedMatcher<typename naked_type<A>::type> *>(
          _matchers[index]);
      if (_matching)
        _matching = matcher->matches(actualArg);
  }
  ....
  const std::vector<Destructible *> _matchers;
};

Диагностическое сообщение PVS-Studio: V522 Возможно, происходит разыменование потенциального нулевого указателя сопоставление. подделка.hpp 6720

Указатель matcher инициализируется значением, возвращаемым dynamic_cast. Однако этот оператор может вернуть nullptr, что очень вероятно. В противном случае используйте более эффективное static_cast вместо dynamic_cast.

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

if (matcher)
  _matching = matcher->matches(actualArg);

Гуилайт

Краткое описание библиотеки GuiLite:

Самая маленькая библиотека GUI, состоящая только из заголовков (4 KLOC) для всех платформ.

#define CORRECT(x, high_limit, low_limit)  {\
  x = (x > high_limit) ? high_limit : x;\
  x = (x < low_limit) ? low_limit : x;\
}while(0)
void refresh_wave(unsigned char frame)
{
  ....
  CORRECT(y_min, m_wave_bottom, m_wave_top);
  ....
}

Диагностическое сообщение PVS-Studio: V529 Нечетная точка с запятой ; после оператора пока. GuiLite.h 3413

Эта ошибка в макросе не вызывает какой-то конкретной проблемы, но это все равно ошибка, поэтому я ее включил.

Макрос должен был использовать классический шаблон do { …. } пока(….). Это позволяет выполнять несколько операций в одном блоке, при этом позволяя вам написать красивую точку с запятой после макроса, как если бы это был вызов функции.

Однако в этом макросе отсутствует ключевое слово do. В результате он распадается, так сказать, на две части: блок кода и пустой, никогда не выполняющийся while(0); петля.

Но почему это плохо, на самом деле?

Ну, во-первых, вы не можете использовать этот макрос в таких конструкциях:

if (A)
  CORRECT(y_min, m_wave_bottom, m_wave_top);
else
  Foo();

Этот код не будет компилироваться, так как он расширится до следующего:

if (A)
  { ..... }
while(0);
else
  Foo();

Думаю, вы согласитесь, что такие дефекты лучше находить и исправлять, пока библиотека еще находится в разработке, чем после релиза. Чтобы убедиться в этом, используйте статический анализ :).

PPLUX

Краткое описание библиотеки PpluX:

Библиотеки C++ с одним заголовком для планирования потоков, рендеринга и т. д.

struct DisplayList {
  DisplayList& operator=(DisplayList &&d) {
    data_ = d.data_;
    d.data_ = nullptr;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. px_render.h 398

Универсальный

Краткое описание универсальной библиотеки:

Цель Universal Numbers, или unums, – заменить систему счисления с плавающей запятой IEEE более эффективной и математически последовательной в средах параллельного выполнения.

Ошибка 1

template<typename Scalar>
vector<Scalar> operator*(double scalar, const vector<Scalar>& v) {
  vector<Scalar> scaledVector(v);
  scaledVector *= scalar;
  return v;
}

Диагностическое сообщение PVS-Studio: V1001 Переменная scaledVector назначена, но к концу функции не используется. вектор.hpp 124

Это опечатка. Функция должна возвращать новый вектор scaledVector, а не исходный вектор v.

Еще одна похожая опечатка: V1001 Переменная «normalizedVector» присваивается, но не используется к концу функции. вектор.hpp 131

Ошибка 2

template<typename Scalar>
class matrix {
  ....
  matrix& diagonal() {
  }
  ....
};

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. матрица.hpp 109

Ошибка 3

template<size_t fbits, size_t abits>
void module_subtract_BROKEN(
  const value<fbits>& lhs, const value<fbits>& rhs, value<abits + 1>& result)
{
  if (lhs.isinf() || rhs.isinf()) {
    result.setinf();
    return;
  }
  int lhs_scale = lhs.scale(),
      rhs_scale = rhs.scale(),
      scale_of_result = std::max(lhs_scale, rhs_scale);
  // align the fractions
  bitblock<abits> r1 =
    lhs.template nshift<abits>(lhs_scale - scale_of_result + 3);
  bitblock<abits> r2 =
    rhs.template nshift<abits>(rhs_scale - scale_of_result + 3);
  bool r1_sign = lhs.sign(), r2_sign = rhs.sign();
  //bool signs_are_equal = r1_sign == r2_sign;
  if (r1_sign) r1 = twos_complement(r1);
  if (r1_sign) r2 = twos_complement(r2);  // <=
  ....
}

Диагностическое сообщение PVS-Studio: V581 Условные выражения операторов if, расположенных рядом, идентичны. Проверить строки: 789, 790. value.hpp 790

Это классическая ошибка копирования-вставки. Программист клонировал следующую строку:

if (r1_sign) r1 = twos_complement(r1);

Изменено r1 на r2:

if (r1_sign) r2 = twos_complement(r2);

Но забыл изменить r1_sign. Вот правильная версия:

if (r2_sign) r2 = twos_complement(r2);

Библиотеки с одним заголовком Chobo

Краткое описание библиотеки Chobo Single-Header Libraries:

Набор небольших однозаголовочных библиотек C++11 от Chobolabs.

Ошибка 1

template <typename T, typename U, typename Alloc = std::allocator<T>>
class vector_view
{
  ....
  vector_view& operator=(vector_view&& other)
  {
    m_vector = std::move(other.m_vector);
  }
  ....
}

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. vector_view.hpp 163

Ошибка 2

template <typename UAlloc>
vector_view& operator=(const std::vector<U, UAlloc>& other)
{
  size_type n = other.size();
  resize(n);
  for (size_type i = 0; i < n; ++i)
  {
    this->at(i) = other[i];
  }
}

Диагностическое сообщение PVS-Studio: V591 Непустая функция должна возвращать значение. vector_view.hpp 184

PGM-индекс

Краткое описание библиотеки PGM-индекс:

Индекс кусочно-геометрической модели (PGM-индекс) – это структура данных, которая обеспечивает быстрый поиск, поиск предшественника, поиск по диапазону и обновление в массивах из миллиардов элементов, используя на несколько порядков меньше места, чем традиционные индексы, обеспечивая при этом тот же наихудший случай. гарантии времени запроса.

Ошибка 1

char* str_from_errno()
{
#ifdef MSVC_COMPILER
  #pragma warning(disable:4996)
  return strerror(errno);
#pragma warning(default:4996)
#else
  return strerror(errno);
#endif
}

Диагностическое сообщение PVS-Studio: V665 Возможно, использование #pragma warning(default: X) в данном контексте некорректно. Вместо этого следует использовать предупреждение #pragma (push/pop). Проверить строки: 9170, 9172. sdsl.hpp 9172

Этот код временно отключает предупреждение компилятора, но делает это некорректно. Такие ошибки можно допустить в пользовательском коде, но уж точно не в библиотеках только для заголовков.

Ошибка 2

template<class t_int_vec>
t_int_vec rnd_positions(uint8_t log_s, uint64_t& mask,
                        uint64_t mod=0, uint64_t seed=17)
{
  mask = (1<<log_s)-1;         // <=
  t_int_vec rands(1<<log_s ,0);
  set_random_bits(rands, seed);
  if (mod > 0) {
    util::mod(rands, mod);
  }
  return rands;
}

Диагностическое сообщение PVS-Studio: V629 Попробуйте проверить выражение ‘1 ‹‹ log_s’. Битовый сдвиг 32-битного значения с последующим расширением до 64-битного типа. sdsl.hpp 1350

Вот одна правильная версия:

mask = ((uint64_t)(1)<<log_s)-1;

Хнсвлиб

Краткое описание библиотеки Hnswlib:

Реализация C++ HNSW только для заголовков с привязками python. Код бумаги для эксперимента HNSW 200M SIFT.

template<typename dist_t>
class BruteforceSearch : public AlgorithmInterface<dist_t> {
public:
  BruteforceSearch(SpaceInterface <dist_t> *s, size_t maxElements) {
    maxelements_ = maxElements;
    data_size_ = s->get_data_size();
    fstdistfunc_ = s->get_dist_func();
    dist_func_param_ = s->get_dist_func_param();
    size_per_element_ = data_size_ + sizeof(labeltype);
    data_ = (char *) malloc(maxElements * size_per_element_);
    if (data_ == nullptr)
      std::runtime_error(
        "Not enough memory: BruteforceSearch failed to allocate data");
    cur_element_count = 0;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V596 Объект создан, но не используется. Ключевое слово throw может отсутствовать: throw runtime_error(FOO); брутфорс.ч 26

Отсутствует оператор throw перед std::runtime_error.

Аналогичная проблема: V596 Объект создан, но не используется. Ключевое слово throw может отсутствовать: throw runtime_error(FOO); брутфорс.ч 161

крошечный днн

Краткое описание библиотеки tiny-dnn:

tiny-dnn — это реализация глубокого обучения на C++14. Он подходит для глубокого обучения на ограниченных вычислительных ресурсах, встроенных системах и устройствах Интернета вещей.

Ошибка 1

class nn_error : public std::exception {
 public:
  explicit nn_error(const std::string &msg) : msg_(msg) {}
  const char *what() const throw() override { return msg_.c_str(); }
 private:
  std::string msg_;
};
inline Device::Device(device_t type, const int platform_id, const int device_id)
  : type_(type),
    has_clcuda_api_(true),
    platform_id_(platform_id),
    device_id_(device_id) {
  ....
#else
  nn_error("TinyDNN has not been compiled with OpenCL or CUDA support.");
#endif
}

Диагностическое сообщение PVS-Studio: V596 Объект создан, но не используется. Ключевое слово throw может отсутствовать: throw nn_error(FOO); устройство.ч 68

nn_error — это не функция, генерирующая исключение, а просто класс, и правильный способ его использования выглядит следующим образом:

throw nn_error("TinyDNN has not been compiled with OpenCL or CUDA support.");

Вот еще один случай неправильного использования этого класса: V596 Объект создан, но не используется. Ключевое слово throw может отсутствовать: throw nn_error(FOO); conv2d_op_opencl.h 136

Ошибка 2

inline std::string format_str(const char *fmt, ...) {
  static char buf[2048];
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
  va_list args;
  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
#ifdef _MSC_VER
#pragma warning(default : 4996)
#endif
  return std::string(buf);
}

Диагностическое сообщение PVS-Studio: V665 Возможно, использование #pragma warning(default: X) в данном контексте некорректно. Вместо этого следует использовать предупреждение #pragma (push/pop). Контрольные строки: 139, 146. утил.ч 146

Длиб

Краткое описание библиотеки Dlib:

Dlib — это современный набор инструментов C++, содержащий алгоритмы машинного обучения и инструменты для создания сложного программного обеспечения на C++ для решения реальных задач.

Ошибка 1

Чтобы было интересно, предлагаю вам найти ошибку самостоятельно.

class bdf_parser
{
public:
  enum bdf_enums
  {
    NO_KEYWORD = 0,
    STARTFONT = 1,
    FONTBOUNDINGBOX = 2,
    DWIDTH = 4,
    DEFAULT_CHAR = 8,
    CHARS = 16,
    STARTCHAR = 32,
    ENCODING = 64,
    BBX = 128,
    BITMAP = 256,
    ENDCHAR = 512,
    ENDFONT = 1024
  };
  ....
  bool parse_header( header_info& info )
  {
    ....
    while ( 1 )
    {
      res = find_keywords( find | stop );
      if ( res & FONTBOUNDINGBOX )
      {
          in_ >> info.FBBx >> info.FBBy >> info.Xoff >> info.Yoff;
          if ( in_.fail() )
              return false;    // parse_error
          find &= ~FONTBOUNDINGBOX;
          continue;
      }
      if ( res & DWIDTH )
      {
          in_ >> info.dwx0 >> info.dwy0;
          if ( in_.fail() )
              return false;    // parse_error
          find &= ~DWIDTH;
          info.has_global_dw = true;
          continue;
      }
      if ( res & DEFAULT_CHAR )
      {
          in_ >> info.default_char;
          if ( in_.fail() )
              return false;    // parse_error
          find &= ~DEFAULT_CHAR;
          continue;
      }
      if ( res & NO_KEYWORD )
          return false;    // parse_error: unexpected EOF
      break;
    }
  ....
};

При удаче?

Вот:

if ( res & NO_KEYWORD )

Диагностическое сообщение PVS-Studio: V616 В побитовой операции используется именованная константа NO_KEYWORD со значением 0. шрифты.cpp 288

Значение именованной константы NO_KEYWORD равно 0. Следовательно, условие не имеет смысла. Вот как должна выглядеть правильная проверка:

if ( res == NO_KEYWORD )

Еще одна неверная проверка: V616 В побитовой операции используется именованная константа NO_KEYWORD со значением 0. шрифты.cpp 334

Ошибка 2

void set(std::vector<tensor*> items)
{
  ....
  epa.emplace_back(new enable_peer_access(*g[0], *g[i]));
  ....
}

Диагностическое сообщение PVS-Studio: V1023 В контейнер epa методом emplace_back добавлен указатель без владельца. Утечка памяти произойдет в случае исключения. tensor_tools.h 1665

Чтобы разобраться, что здесь не так, смотрите документацию V1023.

Ошибка 3

template <
    typename detection_type, 
    typename label_type 
    >
bool is_track_association_problem (
  const std::vector<
    std::vector<labeled_detection<detection_type,label_type> > >& samples
)
{
  if (samples.size() == 0)
    return false;
  unsigned long num_nonzero_elements = 0;
  for (unsigned long i = 0; i < samples.size(); ++i)
  {
    if (samples.size() > 0)
      ++num_nonzero_elements;
  }
  if (num_nonzero_elements < 2)
    return false;
  ....
}

Диагностическое сообщение PVS-Studio: V547 Выражение samples.size() › 0 всегда истинно. свм.ч 360

Это очень, очень странный фрагмент кода! Если цикл начинается, то условие (samples.size() › 0) всегда выполняется. А это значит, что цикл можно упростить:

for (unsigned long i = 0; i < samples.size(); ++i)
{
  ++num_nonzero_elements;
}

Но теперь становится ясно, что петля вам вообще не нужна. Фрагмент можно было бы переписать намного проще и эффективнее:

unsigned long num_nonzero_elements = samples.size();

Но было ли это намерением разработчика? Этот код, безусловно, нуждается в тщательной проверке.

Ошибка 4

class console_progress_indicator
{
  ....
  double seen_first_val;
  ....
};
bool console_progress_indicator::print_status (
  double cur, bool always_print)
{
  ....
  if (!seen_first_val)
  {
    start_time = cur_time;
    last_time = cur_time;
    first_val = cur;
    seen_first_val = true;  // <=
    return false;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V601 Тип bool неявно приводится к типу double. console_progress_indicator.h 136

Значение true сохраняется в члене класса типа double. Хм….

Ошибка 5

void file::init(const std::string& name)
{
  ....
  WIN32_FIND_DATAA data;
  HANDLE ffind = FindFirstFileA(state.full_name.c_str(), &data);
  if (ffind == INVALID_HANDLE_VALUE ||
      (data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) != 0)
  {
    throw file_not_found("Unable to find file " + name);                
  }
  else
  {
    ....
  } 
}

Диагностическое сообщение PVS-Studio: V773 Исключение было сгенерировано без закрытия файла, на который ссылается дескриптор ffind. Возможна утечка ресурсов. dir_nav_kernel_1.cpp 60

Исключение выдается, если каталог найден. Но как насчет закрытия дескриптора файла?

Ошибка 6

Еще одно крайне странное место.

inline double poly_min_extrap(double f0, double d0,
                              double x1, double f_x1,
                              double x2, double f_x2)
{
  ....
  matrix<double,2,2> m;
  matrix<double,2,1> v;
  const double aa2 = x2*x2;
  const double aa1 = x1*x1;
  m =  aa2,       -aa1,
      -aa2*x2, aa1*x1;   
  v = f_x1 - f0 - d0*x1,
      f_x2 - f0 - d0*x2;
  ....
}

Диагностическое сообщение PVS-Studio: V521 Такие выражения с оператором , опасны. Убедитесь, что выражение правильное. оптимизация_line_search.h 211

План состоял в том, чтобы инициализировать матрицы. Но все эти aa2, f_x1, d0 и т. д. — это просто переменные типа double. А это значит, что запятые не разделяют аргументы, которые используются для формирования матриц, а просто действуют как оператор запятая, который возвращает значение правого операнда.

Вывод

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

  • Это поможет вам повысить свою квалификацию. Вы узнаете много нового и полезного, работая с предупреждениями анализатора. Например, см. memset, #pragma warning, emplace_back, strictlyaligned.
  • Опечатки, ошибки и потенциальные уязвимости обнаруживаются на более ранних этапах разработки.
  • Код постепенно становится более качественным, простым и понятным.
  • Вы можете гордиться и хвастаться использованием современных технологий в разработке программного обеспечения :). Ну, это только полушутка. Статический анализ действительно дает вам значительное конкурентное преимущество.

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