Обзор 20-Летия Bittorrent. Время == Качество

Пару недель назад (точнее, 2 июля 2021 года) легендарному протоколу BitTorrent исполнилось двадцать лет. Созданный Брэмом Коэном, протокол быстро развивался с момента своего создания и быстро стал одним из самых популярных методов обмена файлами.

В честь этого почему бы не проверить парочку тематических долгоживущих проектов с помощью анализатора PVS-Studio для Linux?

Обзор 20-летия BitTorrent. Время == качество



Введение

Сегодня у нас на рассмотрении будут два проекта: libtorrent (он же «Rasterbar libtorrent» или «rb-libtorrent») и Transmission. Libtorrent — бесплатная кроссплатформенная библиотека для работы с протоколом BitTorrent, написанная на C++.

На Официальный веб-сайт В списке преимуществ отмечено эффективное использование ресурсов процессора и памяти, а также простота использования.

Судя по английскому вики , около половины доступных на данный момент клиентов BitTorrent основаны на этой библиотеке.

Transmission — кроссплатформенный BitTorrent-клиент с открытым исходный код .

Как и libtorrent, основными преимуществами Transmission являются простота использования и эффективное использование ресурсов.

Кроме того, программа может похвастаться полным отсутствием рекламы, аналитики, платных версий, наличием собственного GUI (графического пользовательского интерфейса) для различных платформ, а также headless-версий (без GUI) для установки на сервера, роутеры и т.д.

Как это проверяли

Для анализа мы использовали Linux-версию статического анализатора PVS-Studio, запущенную в контейнере с Ubuntu 20.04 через WSL2. Для установки необходимо выполнить в консоли следующие команды (для других систем также есть инструкции в документация ):
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

wget -q -O - https://files.viva64.com/etc/pubkey.txt | \ sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update sudo apt-get install pvs-studio

Далее перед проверкой необходимо ввести информацию о вашей лицензии.

Это делается с помощью следующей команды:

pvs-studio-analyzer credentials NAME KEY

(где NAME и KEY — имя и лицензионный ключ соответственно).

Таким образом, лицензия сохранится в каталоге ~/.

config/PVS-Studio/, и вам не придется указывать ее каждый раз при запуске анализатора.

Кстати о лицензиях.

Мы активно поддерживаем разработчиков проектов с открытым исходным кодом и поэтому не только сообщаем об ошибках, найденных в репозитории, но и даем им возможность используйте PVS-Studio бесплатно .

Приглашаем всех остальных скачай и попробуй Анализатор PVS-Studio в действии с использованием временной лицензии :) Для начала анализа воспользуемся самым простым способом — попросим систему сборки сгенерировать файл compile_commands.json (в котором перечислены все параметры и команды, необходимые для сборки проекта).

И передадим в PVS-Studio для анализа.

С этой целью во время сборки мы добавим аргумент -DCMAKE_EXPORT_COMPILE_COMMANDS=On к вызову cmake. Например:

cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On .



И наконец, приступим к анализу.

Для этого в папке, содержащей сгенерированный файл compile_commands.json, выполните следующую команду:

pvs-studio-analyzer analyze -o transmission.log -j 8

где ключ -o указывает файл для сохранения результатов работы анализатора, а флаг -j позволяет распараллелить анализ необходимого количества потоков.

Если описанный выше способ взаимодействия с PVS-Studio вас не устраивает, то у нас есть документация Приведены примеры взаимодействия с различными системами сборки и компиляторами.

Еще один примечательный момент – использование формата SARIF для просмотра отчета анализатора.

Особенно это актуально для разработчиков, предпочитающих редактор кода Visual Studio, поскольку для него существует расширение.

Шариф Зритель , что позволяет просмотреть отчет и прямо из него перейти к затронутым в коде местам.

На скриншоте ниже вы можете увидеть, как визуально выглядела проверка проекта Transmission.

Обзор 20-летия BitTorrent. Время == качество

Чтобы создать отчет в формате SARIF при работе с PVS-Studio в Linux, после запуска анализатора необходимо выполнить следующую команду:

plog-converter -t sarif -o .

/transmission.sarif .

/transmission.log -d V1042

где с помощью -t sarif мы указываем, что результат должен быть сохранен в формате SARIF. Флаг -o указывает имя файла отчета.

А флагом -d мы подавляем неактуальную в данном случае диагностику.

Подробнее об открытом стандарте обмена результатами статического анализа (SARIF) можно прочитать на сайте.

ОАЗИС Открытый .

Пример взаимодействия с GitHub можно найти в нашей статье " Как просмотреть красивые отчеты об ошибках на GitHub с помощью SARIF ".



Результаты теста

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

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

увы.

Проекты небольшие по объему, и видно, что ими занимаются опытные разработчики.

Кроме того, упоминания об использовании сторонних статические анализаторы (Покрытие, Cppcheck).

Но даже несмотря на все это, PVS-Studio удалось найти пару интересных ошибок.



Передача инфекции

Начнем, пожалуй, с проекта Transmission, как самого популярного и часто используемого.

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



Фрагмент №1: использование memset для очистки памяти



static void freeMetaUI(gpointer p) { MakeMetaUI* ui = p; tr_metaInfoBuilderFree(ui->builder); g_free(ui->target); memset(ui, ~0, sizeof(MakeMetaUI)); g_free(ui); }

Предупреждение В597 Компилятор может удалить вызов функции «memset», который используется для очистки объекта «ui».

Функцию memset_s() следует использовать для удаления личных данных.

makemeta-ui.c:53 Уже много раз говорилось и написано немало статей о граблях, на которые периодически наступают разработчики при использовании функции memset для очистки памяти.

Короче говоря, компилятор имеет полное право удалить вызовы memset, если сочтет их бессмысленными (часто это происходит, когда буфер очищается в конце операции и больше не используется).

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

Обзор 20-летия BitTorrent. Время == качество

Как видно из рисунка выше, компилятор Clang 12.0.1 действительно вырезает вызов memset даже при использовании флага компиляции -O2. Многие скажут: «Ну удалил и удалил, чего мямлить», но проблема в том, что личные данные пользователя могут и не стереться.

Согласен, не думаю, что проблема конфиденциальности данных будет актуальна для торрент-клиента.

Но если разработчик написал это здесь, то что ему помешает сделать то же самое в более ответственном месте? Чтобы этого избежать, нужно использовать специально разработанные функции (например, memset_s или Ртлсекурезеропамять ).

Мои коллеги уже писали об этой проблеме более подробно.

один раз , два И три .



Фрагмент N2: ошибки в библиотеках — тоже ошибки



void jsonsl_jpr_match_state_init(jsonsl_t jsn, jsonsl_jpr_t *jprs, size_t njprs) { size_t ii, *firstjmp; .

jsn->jprs = (jsonsl_jpr_t *)malloc(sizeof(jsonsl_jpr_t) * njprs); jsn->jpr_count = njprs; jsn->jpr_root = (size_t*)calloc(1, sizeof(size_t) * njprs * jsn->levels_max); memcpy(jsn->jprs, jprs, sizeof(jsonsl_jpr_t) * njprs); /* Set the initial jump table values */ firstjmp = jsn->jpr_root; for (ii = 0; ii < njprs; ii++) { firstjmp[ii] = ii+1; } }

Предупреждение В575 Потенциальный нулевой указатель передается в функцию memcpy. Проверьте первый аргумент. Проверить строки: 1142, 1139. jsonsl.c:1142. Предупреждение В522 Возможно, произошло разыменование потенциального нулевого указателя firstjmp. Проверить строки: 1147, 1141. jsonsl.c:1147. Этот фрагмент скрывает сразу две проблемы, и обе связаны с отсутствием проверки указателя, полученного от функции malloc/calloc. Да, возможно, эта ошибка вообще никогда себя не проявит, но именно этот код желательно исправить.

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

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

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

Анализатор также выявил аналогичные подозрительные фрагменты кода:

  • V522 Возможно, произошло разыменование потенциального нулевого указателя «jsn».

    Проверьте строки: 117, 113. jsonsl.c:117.

  • V522 Возможно, произошло разыменование потенциального нулевого указателя «i».

    ПодробностиDialog.cc:133

  • V522 Возможно, произошло разыменование потенциального нулевого указателя.

    ТоррентФильтер.

    cc:320



libtorrent

На этом пожалуй закончим с Transmission и посмотрим, что интересного нашлось в проекте libtorrent.

Фрагмент N1: Недостаточная проверка индексов массива



template <typename Handler> void handshake2(error_code const& e, Handler h) { .

std::size_t const read_pos = m_buffer.size(); .

if (m_buffer[read_pos - 1] == '\n' && read_pos > 2) // <= { if (m_buffer[read_pos - 2] == '\n') { found_end = true; } else if (read_pos > 4 && m_buffer[read_pos - 2] == '\r' && m_buffer[read_pos - 3] == '\n' && m_buffer[read_pos - 4] == '\r') { found_end = true; } } .

}

Предупреждение В781 Значение индекса read_pos проверяется после его использования.

Возможно, ошибка в логике программы.

http_stream.hpp:166. Классическая ошибка, при которой разработчик сначала пытается получить элемент массива m_buffer по индексу read_pos — 1, и только потом read_pos проверяется на корректность (read_pos > 2).

Трудно сказать, что произойдет на практике в этом случае: возможно, будет прочитана другая переменная, а может быть, произойдет Access Violation. Ведь Undefine Behavior не просто так назвали undefined :) Правильным решением здесь было бы поменять местами эти действия:

if (read_pos > 2 && m_buffer[read_pos - 1] == '\n')



Фрагмент N2, N3: перезапись значений



void dht_tracker::dht_status(session_status& s) { s.dht_torrents += int(m_storage.num_torrents()); // <= s.dht_nodes = 0; s.dht_node_cache = 0; s.dht_global_nodes = 0; s.dht_torrents = 0; // <= s.active_requests.clear(); s.dht_total_allocations = 0; for (auto& n : m_nodes) n.second.dht.status(s); }

Предупреждение В519 Переменной 's.dht_torrents' присваиваются значения два раза подряд. Возможно, это ошибка.

Проверьте строки: 205, 210. dht_tracker.cpp 210. В приведенном выше фрагменте переменная s.dht_torrents изменяется дважды: в первый раз ей присваивается значение, а через несколько строк она сбрасывается в ноль без использования между назначениями.

То есть мы имеем дело с так называемой тупиковой записью (Dead Store).

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

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

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

При этом здесь есть три перезаписанные переменные, и одна из них получает то же значение, что и до условия.

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

void torrent::bytes_done(torrent_status& st, status_flags_t const flags) const { .

st.total_done = 0; st.total_wanted_done = 0; st.total_wanted = m_size_on_disk; .

if (m_seed_mode || is_seed()) { st.total_done = m_torrent_file->total_size() - m_padding_bytes; st.total_wanted_done = m_size_on_disk; st.total_wanted = m_size_on_disk; .

return; } else if (!has_picker()) { st.total_done = 0; st.total_wanted_done = 0; st.total_wanted = m_size_on_disk; return; } .

}

Предупреждения PVS-Studio:

  • V1048 Переменной st.total_wanted присвоено то же значение.

    торрент.cpp 3784

  • V1048 Переменной st.total_done было присвоено то же значение.

    торрент.cpp 3792

  • V1048 Переменной st.total_wanted_done было присвоено то же значение.

    торрент.cpp 3793

  • V1048 Переменной st.total_wanted присвоено то же значение.

    торрент.cpp 3794



Фрагмент N4: Не удалось явное приведение типа.



void torrent::get_download_queue(std::vector<partial_piece_info>* queue) const { .

const int blocks_per_piece = m_picker->blocks_in_piece(piece_index_t(0)); .

int counter = 0; for (auto i = q.begin(); i != q.end(); ++i, ++counter) { partial_piece_info pi; .

pi.blocks = &blk[std::size_t(counter * blocks_per_piece)]; } }

Предупреждение В1028 Возможен перелив.

Рассмотрите возможность приведения операндов оператора counter *blocks_per_piece к типу size_t, а не к результату.

торрент.cpp 7092 В этом случае явное приведение типа к size_t используется для правильного доступа к элементам массива.

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

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

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

pi.blocks = &blk[std::size_t(counter) * blocks_per_piece];

Подобные проблемы также были обнаружены в следующих фрагментах:

  • V1028 Возможно переполнение.

    Рассмотрите возможность приведения операндов оператора new_size_words + 1 к типу size_t, а не к результату.

    битфилд.cpp 179

  • V1028 Возможно переполнение.

    Рассмотрите возможность приведения операндов оператора m_capacity + sum_to_grow к типу size_t, а не к результату.

    гетерогенный_queue.hpp 207



Фрагмент N5: дополнительные условия

В проекте libtorrent, как и в Transmission, было много предупреждений, связанных с ненужными условиями.

Их нельзя назвать ложными, но и перечислять их нет смысла, ибо интересного в этом мало.

Чтобы было понятно, что именно я имею в виду, вот кусок кода:

char const* operation_name(operation_t const op) { .

static char const* const names[] = { .

}; int const idx = static_cast<int>(op); if (idx < 0 || idx >= int(sizeof(names) / sizeof(names[0]))) return "unknown operation"; return names[idx]; }

Предупреждение В560 Часть условного выражения всегда ложна: idx < 0. alert.cpp 1885. В нем анализатор справедливо предупреждает, что условие idx < 0 does not make sense, since the index variable receives its value from an enumeration that contains only unsigned integers:

enum class operation_t : std::uint8_t

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

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



Заключение

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

Думаю, важную роль здесь играет тот факт, что проекты существуют довольно давно, активно развиваются сообществом open source и, судя по истории коммитов, ранее проверялись статическими анализаторами.

Ну и напоследок хотелось бы еще раз напомнить, что команда PVS-Studio любит и активно поддерживает проекты с открытым исходным кодом.

Именно поэтому мы не только сообщаем разработчикам о найденных ошибках, но и даем им возможность используйте PVS-Studio бесплатно .

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

Для коммерческих проектов мы предлагаем скачай и попробуй Анализатор PVS-Studio, запросив пробную лицензию на нашем сайте :) Если вы хотите поделиться этой статьей с англоязычной аудиторией, воспользуйтесь ссылкой для перевода: Михаил Гельвих.

Проверка BitTorrent в честь 20-летия.

Время == качество .

Теги: #открытый исходный код #C++ #OpenSource #pvs-studio #статический анализ #torrent #BitTorrent #передача #проверка проекта #libtorrent

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.