Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
По следам калькуляторов: SpeedCrunch

По следам калькуляторов: SpeedCrunch

19 Мар 2019

Исследование кода калькуляторов продолжается! В этом обзоре будет рассмотрен проект SpeedCrunch - второй по популярности среди бесплатных калькуляторов.

0618_SpeedCrunch_ru/image1.png

Введение

SpeedCrunch - это высокоточный научный калькулятор с быстрым пользовательским интерфейсом, управляемый с клавиатуры. Это бесплатное программное обеспечение с открытым исходным кодом, доступное на Windows, Linux и macOS.

Исходный код размещён на BitBucket. Мне не очень понравилась документация по сборке, которую, на мой взгляд, стоило бы написать подробнее. В требованиях указан "Qt 5.2 or later", хотя понадобилось несколько конкретных пакетов, о которых было непросто узнать из лога CMake. Кстати, сейчас хорошей практикой считается прикладывать Dockerfile к проекту для быстрой настройки нужного окружения разработчика.

Для сравнения с другими калькуляторами привожу вывод утилиты Cloc:

0618_SpeedCrunch_ru/image2.png

Обзоры ошибок в других проектах:

В качестве инструмента статического анализа использовался PVS-Studio. Это комплекс решений для контроля качества кода, поиска ошибок и потенциальных уязвимостей. В поддерживаемые языки входят: C, C++, C# и Java. Запуск анализатора возможен на Windows, Linux и macOS.

Странная логика в цикле

V560 A part of conditional expression is always true: !ruleFound. evaluator.cpp 1410

void Evaluator::compile(const Tokens& tokens)
{
  ....
  while (!syntaxStack.hasError()) {
    bool ruleFound = false;                                     // <=

    // Rule for function last argument: id (arg) -> arg.
    if (!ruleFound && syntaxStack.itemCount() >= 4) {           // <=
        Token par2 = syntaxStack.top();
        Token arg = syntaxStack.top(1);
        Token par1 = syntaxStack.top(2);
        Token id = syntaxStack.top(3);
        if (par2.asOperator() == Token::AssociationEnd
            && arg.isOperand()
            && par1.asOperator() == Token::AssociationStart
            && id.isIdentifier())
        {
            ruleFound = true;                                   // <=
            syntaxStack.reduce(4, MAX_PRECEDENCE);
            m_codes.append(Opcode(Opcode::Function, argCount));
#ifdef EVALUATOR_DEBUG
                dbg << "\tRule for function last argument "
                    << argCount << " \n";
#endif
            argCount = argStack.empty() ? 0 : argStack.pop();
        }
    }
    ....
  }
  ....
}

Обратите внимание на переменную ruleFound. На каждой итерации ей выставляется значение false. Но если посмотреть тело всего цикла, то при определённых условиях этой переменной выставляется значение true, но оно не будет учитываться на новой итерации цикла. Скорее всего, переменную ruleFound необходимо было объявить перед циклом.

Подозрительные сравнения

V560 A part of conditional expression is always true: m_scrollDirection != 0. resultdisplay.cpp 242

void ResultDisplay::fullContentScrollEvent()
{
  QScrollBar* bar = verticalScrollBar();
  int value = bar->value();
  bool shouldStop = (m_scrollDirection == -1 && value <= 0) ||
                    (m_scrollDirection == 1 && value >= bar->maximum());

  if (shouldStop && m_scrollDirection != 0) {     // <=
      stopActiveScrollingAnimation();
      return;
  }

  scrollLines(m_scrollDirection * 10);
}

Если переменная shouldStop будет иметь значение true, то переменная m_scrollDirection будет иметь одно из двух значений: -1 или 1. Следовательно, в следующем условном операторе значение переменной m_scrollDirection точно будет не равно нулю, о чём и предупреждает анализатор.

V668 There is no sense in testing the 'item' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. editor.cpp 998

void EditorCompletion::showCompletion(const QStringList& choices)
{
  ....
  for (int i = 0; i < choices.count(); ++i) {
    QStringList pair = choices.at(i).split(':');
    QTreeWidgetItem* item = new QTreeWidgetItem(m_popup, pair);

    if (item && m_editor->layoutDirection() == Qt::RightToLeft)
        item->setTextAlignment(0, Qt::AlignRight);
    ....
  }
  ....
}

Память для объекта типа QTreeWidgetItem выделяется с помощью оператора new. Это означает, что в случае невозможности выделения динамической памяти, будет выброшено исключение std::bad_alloc(). Поэтому проверка указателя item является лишней и её можно удалить.

Потенциальный NULL Dereference

V595 The 'ioparams' pointer was utilized before it was verified against nullptr. Check lines: 969, 983. floatio.c 969

int cattokens(....)
{
  ....
  if (printexp)
  {
    if (expbase < 2)
      expbase = ioparams->expbase;  // <=
    ....
  }
  dot = '.';
  expbegin = "(";
  expend = ")";
  if (ioparams != NULL)            // <=
  {
    dot = ioparams->dot;
    expbegin = ioparams->expbegin;
    expend = ioparams->expend;
  }
  ....
}

Указатель ioparams разыменовывается до того, как проверяется на валидность. Скорее всего, в код закралась потенциальная ошибка. Так как разыменование находится под несколькими условиями, то проблема может проявлять себя редко, но метко.

Деление на ноль

V609 Divide by zero. Denominator range [0..4]. floatconvert.c 266

static int
lgbase( signed char base)
{
  switch(base)
  {
    case 2:
      return 1;
    case 8:
      return 3;
    case 16:
      return 4;
  }
  return 0;                                       // <=
}

static void
_setlongintdesc(
  p_ext_seq_desc n,
  t_longint* l,
  signed char base)
{
  int lg;

  n->seq.base = base;
  lg = lgbase(base);                              // <=
  n->seq.digits = (_bitlength(l) + lg - 1) / lg;  // <=
  n->seq.leadingSignDigits = 0;
  n->seq.trailing0 = _lastnonzerobit(l) / lg;     // <=
  n->seq.param = l;
  n->getdigit = _getlongintdigit;
}

Функция lgbase допускает возврат нулевого значения, на которое потом выполняется деление. Потенциально, в функцию могут передать что-нибудь кроме значений 2, 8 и 16.

Неопределённое поведение

V610 Undefined behavior. Check the shift operator '<<'. The left operand '(~0)' is negative. floatlogic.c 64

static char
_signextend(
  t_longint* longint)
{
  unsigned mask;
  signed char sign;

  sign = _signof(longint);
  mask = (~0) << SIGNBIT;  // <=
  if (sign < 0)
    longint->value[MAXIDX] |= mask;
  else
    longint->value[MAXIDX] &= ~mask;
  return sign;
}

Результат инверсии нуля помещается в знаковый тип int, поэтому результатом будет отрицательное число, для которого потом выполняется сдвиг. Сдвиг отрицательного числа влево является неопределённым поведением.

Весь список опасных мест:

  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(- 1)' is negative. floatnum.c 289
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(- 1)' is negative. floatnum.c 325
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(- 1)' is negative. floatnum.c 344
  • V610 Undefined behavior. Check the shift operator '<<'. The left operand '(- 1)' is negative. floatnum.c 351

Незакрытые HTML-теги

V735 Possibly an incorrect HTML. The "</body>" closing tag was encountered, while the "</div>" tag was expected. book.cpp 127

static QString makeAlgebraLogBaseConversionPage() {
  return
    BEGIN
    INDEX_LINK
    TITLE(Book::tr("Logarithmic Base Conversion"))
    FORMULA(y = log(x) / log(a), log<sub>a</sub>x = log(x) / log(a))
    END;
}

Как это часто бывает с C/C++ кодом - из исходника ничего не понятно, поэтому обратимся к препроцессированному коду для этого фрагмента:

0618_SpeedCrunch_ru/image3.png

Анализатор обнаружил незакрытый тег div. В этом файле много фрагментов html-кода и теперь его следует дополнительно проверить разработчикам.

Вот ещё подозрительные места, которые удалось найти с помощью PVS-Studio:

  • V735 Possibly an incorrect HTML. The "</td>" closing tag was encountered, while the "</sub>" tag was expected. book.cpp 344
  • V735 Possibly an incorrect HTML. The "</td>" closing tag was encountered, while the "</sub>" tag was expected. book.cpp 347

Оператор присваивания

V794 The assignment operator should be protected from the case of 'this == &other'. quantity.cpp 373

Quantity& Quantity::operator=(const Quantity& other)
{
  m_numericValue = other.m_numericValue;
  m_dimension = other.m_dimension;
  m_format = other.m_format;
  stripUnits();
  if(other.hasUnit()) {
    m_unit = new CNumber(*other.m_unit);
    m_unitName = other.m_unitName;
  }
  cleanDimension();
  return *this;
}

Рекомендуется рассмотреть ситуацию, когда объект присваивается сам себе, сравнив указатели. Другими словами, в начало тела функции стоит добавить следующие две строчки кода:

if (this == &other)
  return *this;

Напоминание

V601 The 'false' value is implicitly cast to the integer type. cmath.cpp 318

/**
 * Returns -1, 0, 1 if n1 is less than, equal to, or more than n2.
 * Only valid for real numbers, since complex ones are not an ordered field.
 */
int CNumber::compare(const CNumber& other) const
{
  if (isReal() && other.isReal())
    return real.compare(other.real);
  else
    return false; // FIXME: Return something better.
}

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

Заключение

Уже доступны обзоры трёх калькуляторов: Windows Calculator, Qalculate! и SpeedCrunch. Мы готовы продолжить исследовать код популярных калькуляторов. Вы можете предлагать проекты для проверки, так как рейтинги программного обеспечения не всегда отражают реальную картину.

Проверь свой "Калькулятор", скачав PVS-Studio и попробовав на своём проекте :-)

Популярные статьи по теме
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

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

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

Автор: Андрей Карпов

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

Дата: 31 Май 2014

Автор: Андрей Карпов

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

Автор: Андрей Карпов

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
Любите статический анализ кода!

Дата: 16 Окт 2017

Автор: Андрей Карпов

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.
Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Дата: 31 Июл 2017

Автор: Андрей Карпов

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода)…
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

Автор: Андрей Карпов

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…

Комментарии (0)

Следующие комментарии

На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять