![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
Thunderbolt |
|
||||||||||||||||||||||||||||||||||||||||||||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
![]() Аннотация Проверив код ReactOS, я смог исполнить сразу три своих желания. Во-первых, давно хотелось написать статью об обыкновенном проекте. Не интересно проверять код таких проектов, как Chromium. Он слишком качественен и, на поддержание этого качества тратятся ресурсы, недоступные в обыкновенных проектах. Во-вторых, появился хороший пример, на котором можно показать, как необходим статический анализ в большом проекте, особенно если он разрабатывается разнородным распределенным коллективом. В-третьих, я получил подтверждение, что PVS-Studio становится всё лучше и полезнее. PVS-Studio становится все лучше и лучше Начну с последнего момента, по поводу пользы инструмента PVS-Studio. ReactOS косвенно подтверждает, что PVS-Studio развивается в правильном направлении. Вот новость о проверке ReactOS с помощью такого тяжеловеса, как Coverity - "Статический анализ в Coverity" [1]. Я, конечно, знаю и понимаю, что до возможностей Coverity нам далеко. Но, тем не менее, там, где благодаря Coverity "было найдено несколько новых ошибок", PVS-Studio находит их целый вагон и маленькую тележку. При этом никакой код никуда отправлять не надо. Можно просто взять и проверить проект. Значит мы на верном пути. Что такое ReactOS? ReactOS - это современная, свободная и открытая операционная система, основанная на архитектуре Windows XP/2003. Система была написана с нуля и имеет своей целью повторение архитектуры Windows-NT, созданной Microsoft, от аппаратного до прикладного уровня. Объем исходного кода на языке Си, Си++ и ассемблере составляет порядка 220 мегабайт. Различные ссылки:
Ошибки в ReactOS Теперь поговорим о вагоне ошибок, которые повстречались мне в коде ReactOS. Конечно, все они в статью не войдут. Здесь я выложил текстовый файл с описанием ошибок, которые я заметил в ходе анализа. Файл содержит диагностические сообщения с именами файлов и номерами строк. Также я выделил ошибки в короткий код и дал некоторые комментарии. Поэтому тем, кто захочет сделать правки в ReactOS, лучше руководствоваться этим файлом, а не статьёй. А еще лучше скачать и самим проверить проект с помощью PVS-Studio. Ведь я не знаком с проектом и выписывал только те ошибки, которые мне понятны. По поводу многих фрагментов кода я не знаю, содержат они ошибки или нет. Так что мой анализ достаточно поверхностен. Ключ для проверки выделим. Ошибки, которые можно встретить в ReactOS разнообразнейшие. Просто зоопарк. Есть опечатки из одного символа.
На самом деле должно быть написано вот так: "mii->cch = miiW->cch;". Потеряли букву 'W'. Как результат, программы уже не могут доверять функции GetMenuItemInfoA. А вот другая опечатка в один символ. В этот раз некорректно работает сравнение двух имён.
Есть путаница между оператором && и &. Очень распространенная ошибка. Встречаю практически в каждом проекте, где работают с битами или атрибутами файлов.
Корректный код должен выглядеть так "(This->options & ACO_AUTOSUGGEST)". Пример ниже содержит родственную ей ошибку, из-за которой всё условие всегда ложно.
Если присмотреться, то можно заметить коварный фрагмент: "|| EWOULDBLOCK ||". Кстати, в ReactOS нашлось достаточно много условий, которые всегда истинны или ложны. Некоторые нестрашные, так как, например, располагаются в макросе assert(). Но есть, на мой взгляд, и критичные.
Согласитесь, что реализация таких функций как "connect" должна быть протестирована максимально полно. А здесь мы имеем условие, которое всегда ложно. Быстро заметить дефект не так просто, поэтому выделю суть ошибки:
Кстати, часть, связанная с сокетами, вообще выглядит сырой. Возможно, это связано с тем, что в Linux мире SOCKET принято объявлять как знаковый тип. А в Windows он беззнаковый:
Как результат имеем разнообразные ошибки в операциях сравнения:
Выражение "ads->tcpsocket >= 0" не имеет смысла, так как всегда истинно. Встречаются просто странные фрагменты. Скорее всего, это недописанные и забытые участки кода.
Зачем вызывать "strcmpW", если результат никак не используется? Имеются ошибки, связанные с приоритетом операций.
Я расставлю скобки, чтобы стало ясно, как работает это выражение на самом деле:
Следующая ошибка обязательно встречается в любом большом проекте. Есть парочка и в ReactOS. Речь идет о лишней точке с запятой - ';'.
Ещё мне нравятся ошибки с инициализацией элементов массива. Не знаю почему. Они трогательны. Возможно, я вспоминаю свои первые эксперименты с массивами на языке Basic.
Можно продолжать приводить разные интересные участки кода. К сожалению, тогда статья станет слишком длинной и нужно останавливаться. Напомню, что посмотреть на другие ошибки, найденные мной в ReactOS, можно вот в этом файле. На сладкое только приведу вот этот кусочек кода:
Пример использования:
Это – шедевр. Статический анализ кода Я считаю ReactOS очень хорошим примером проекта, где регулярный статический анализ кода просто необходим. И дело здесь не в квалификации разработчиков. Проект большой и содержит разнообразные подсистемы. Это значит, что над таким проектом всегда трудится большое количество людей. А в большом коллективе всегда кто-то программирует хуже, кто-то лучше. Кто-то использует один стиль, кто-то другой. Но никто не застрахован от ошибок. Вот смотрите. Один человек взял и написал в ReactOS вот так:
Код делает не то, что хочется. Корректный вариант: if ((res = setsockopt(....)) == -1). Если придерживаться практики писать константу в начале, то случайно никогда не сделаешь ошибочное присваивание внутри оператора "if". У нас здесь другой тип ошибки. Но если писать код по приведенному правилу, то не получится сделать ошибку и в рассматриваемом выражении: "if (-1 == res = setsockopt(....))". Вот только знание этой практики не мешает другому человеку ошибиться альтернативным способом.
Здесь вначале красиво написана константа 0. Вот только круглая скобка закрывается не там, где надо. Обыкновенная опечатка. К чему я все эти примеры? А к тому, что никто из нас программистов не идеален. И ни стандарт кодирования, ни технологии программирования, ни самоконтроль не гарантируют отсутствие ошибок в коде. В крупных проектах просто необходимы такие вспомогательные технологии, как динамический и статический анализ. Подчеркну: Считаю, что статический анализ кода должен являться обязательным элементом цикла разработки ReactOS и других крупных проектов. Поясню своё утверждение. В подобных системах невозможно приблизиться к 100% покрытию кода при тестировании с помощью юнит-тестов или регрессионных тестов. Немного уточню. Можно конечно, но затраты на создание и поддержание таких тестов становятся недопустимо высоки. Причина в том, что уж очень велико количество возможных состояний системы и возможных путей выполнения ветвей кода. Некоторые ветви крайне редко получают управление, но от этого не становятся ненужными. Именно здесь и получается увидеть преимущество, которым обладает статический анализ. Он проверяет весь код, в независимости от того, как часто он получает управление в процессе работы программы. Пример проверки кода, редко получающего управление:
Скорее всего, изначально код был написан неправильно. Потом кто-то заметил, что сообщение формируется неправильно и внес исправление, написав "%I64u". А вот на код по соседству он не обратил внимание. И там по-прежнему имеется некорректный формат "%ull". Видимо ветка вызывается крайне редко. Статический анализ такое не пропустит. Собственно он и не пропустил, раз я могу продемонстрировать этот пример. Другим хорошим примером может служить большое количество ошибок очистки памяти, которые я увидел ReactOS. Почему их так много, мне понятно. Никто не тестирует, заполнилась память или нет. Во-первых, сложно осознать, что в этих простых местах можно ошибиться. А во-вторых, не так просто проверить очистился внутри функции какой-то временный буфер или нет. Здесь статический анализ вновь на высоте. Приведу только пару примеров. На самом деле я насчитал как минимум 13 ошибок заполнения массивов константным значением.
Очищаем только первые байты массива, так как sizeof(context) возвращает размер указателя, а не структуры.
Перепутаны аргументы при использовании макроса RtlFillMemory. Вызов должен быть таким:
Снова о табуляциях и пробелах Заранее хочу попросить не начинать в комментариях новую бурную дискуссию на эту тему. Я просто выскажу своё мнение. С ним можно быть согласным или нет. Но обсуждать не стоит. Есть два непримиримых лагеря. Одни за использование табуляции в коде, так как это позволяет подстраивать отображение кода под себя [2]. Другие говорят, что это всё равно не работает и, что нет разумных причин использовать символы табуляции. От табуляции только вред и разъезжающееся форматирование. К их числу отношусь и я [3]. Можно сколько угодно говорить о том, что табуляции надо использовать правильно и тогда всё будет хорошо. К сожалению, это говорят те, кто работает замкнуто над одним проектом и не сталкиваются с внешним миром. В любом открытом или просто большом проекте, не удаётся достичь нормального оформления кода, если разрешить использовать табуляцию в любом виде. Я не буду заниматься абстрактными рассуждениями. В этот раз я просто приведу своим оппонентам наглядный пример. Сейчас таким примером станет код ReactOS. В стандарте кодирования ReactOS [4] написано хорошее правило с теоретической точки зрения: Generic note about TABs usage: Don't use TABs for formatting; use TABs for indenting only and use only spaces for formatting.
Поклонники табуляции довольны. Однако я открываю исходные коды ReactOS и наблюдаю во многих местах испорченное форматирование. Почему? ![]() ![]() ![]() Ответ конечно очевиден. Потому, что это сложно помнить, где надо нажать TAB, а где поставить несколько пробелов, если это не единственный проект, с которым работаешь. Вот люди постоянно и ошибаются. А раз так, нечего быть теоретиками, а надо быть практиками. Надо взять и запретить использовать табуляцию вообще. И тогда всё у всех будет одинаково, а виновника, кто начинает использовать табуляцию, легко найти и сделать ему внушение. Это не шаг назад в оформлении кода! Это шаг вперёд! Это следующий уровень осознания. Теоретическая красота настраиваемых отступов не сочетается с практикой. В первую очередь важно обеспечить однозначное представление кода и легкость разработки в большой команде. В компании Google это понимают. И в их стандарте для форматирования используются только пробелы [5]. Тем, кто ратует за использование табуляции, еще раз рекомендую задумываться, почему распределенная команда высококлассных профессионалов, которые разрабатывают Chromium, выбрала именно пробелы. И еще раз. Теоретическая красота настраиваемых отступов не сочетается с практикой. Неважно, как красиво звучит теория, если она не работает. А именно так и обстоит дело в ReactOS. Рекомендую команде разрабатывающей ReactOS модифицировать их стандарт и отказаться от использования табуляции. Любая табуляция должна расцениваться, как ошибка и быть устранена из кода. Кстати, подобная практика позволит находить вот такие вот безобразия в коде ReactOS:
Последнее сравнение, это сравнение с символом табуляции, а не с пробелом, как может показаться. Нормальный код должен выглядеть так: "(*DebugOptionEnd == '\t')". Примечание для сторонников табуляции. Не надо мне вновь рассказывать, как правильно использовать табуляции. И это не мой код. Смотрите, вот есть вполне конкретный проект ReactOS. В нем есть плохо отформатированный код. Подумайте, какие действия позволят сделать так, чтобы новый программист мог открыть код проекта и не гадать, какой размер символа табуляции ему необходимо установить в настройках редактора. Мысли в духе "нужно сразу было писать правильно" практической ценности не имеют. Библиографический список
--------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
borisbn |
|
||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 4875 Регистрация: 6.2.2010 Где: Ростов-на-Дону Репутация: 22 Всего: 135 |
Это-то, конечно, так, но IMHO придерживаться этого правила всегда и везде - глупо. В простых случаях
"читается" как "Если count равен нулю, то ...". Обратная же запись "не читаема". А на ошибку типа
ругнётся компилятор. А по поводу пробелов и табуляции - полностью и безоговорочно согласен. Своим падаванам по рукам даю за табуляцию. -------------------- Женщины отличаются от программистов тем, что у них чары состоят из стрингов |
||||||
|
|||||||
alexvs11 |
|
|||
hell is here ![]() ![]() Профиль Группа: Участник Сообщений: 518 Регистрация: 21.8.2010 Репутация: 6 Всего: 10 |
||||
|
||||
fish9370 |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 663 Регистрация: 15.4.2007 Где: Москва Репутация: -1 Всего: 1 |
если у них не будет таких ошибок, то какой же это будет windows? а по поводу табуляций, я придерживаюсь спецификации Kernighan & Ritchie, где рекомендуется использовать табуляцию шириной 8 символов - прекрасный стиль, применяется в ядре линукса, в астериске, и в тысячах других програм.. пробелы вместо табуляций появляются не потому-что, кто-то лепит их вместо табуляций, а потому-что используется копи-паст, это и порождает пробелы.. - что легко исправляется с помощью утилиты indent. А редактаровать код с пробелами вместо табуляций - адское занятие, если это не какой-то специальный редактор, который воспринимает пробелы как табуляцию.. писать константу впереди - это маразм - как тут уже правильно заметили, читать такой код невозможно.. Добавлено через 7 минут и 41 секунду gcc на это ругнется, только если будет опция -Wall, что и рекомендую включать.. -------------------- undefined |
|||
|
||||
volatile |
|
||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 37 Всего: 85 |
Слегка придирусь чтоли, если позволите...
В обоих случаях условие абсолютно одинаково. там и там сравнивается возвращаемое значение функции setsockopt() с минус единицей. Разница лишь в значении которое присваивается переменной res, а чему оно должно быть равно, из приведенного отрывка абсолютно не очевидно.
Как не меняй местами, а у оператора "== " приоритет всегда выше чем у оператора "=" Так что рекомендуемый вами код, будет интерпретирован как ((-1 == res) = setsockopt(....))" То есть сначала вычислится булева переменная (-1 == res), а потом ей присвоится значение функции setsockopt(); Что смысла вообще не имеет... |
||||||
|
|||||||
alexvs11 |
|
|||
hell is here ![]() ![]() Профиль Группа: Участник Сообщений: 518 Регистрация: 21.8.2010 Репутация: 6 Всего: 10 |
|
|||
|
||||
fish9370 |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 663 Регистрация: 15.4.2007 Где: Москва Репутация: -1 Всего: 1 |
отсюда вывод - скобки нужно правильно ставить, этот метод лишь маскирует проблему.. -------------------- undefined |
|||
|
||||
volatile |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 37 Всего: 85 |
||||
|
||||
Thunderbolt |
|
||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Ох уж мне эти сказочники.... error C2106: '=' : left operand must be l-value --------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||
|
|||||
volatile |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 37 Всего: 85 |
||||
|
||||
volatile |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 37 Всего: 85 |
К томуже, почему вы решили что ?
А может так и задумывалось? т.е.:
Ну да ладно, завязываю.. |
||||
|
|||||
Thunderbolt |
|
|||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Вытекает. Но не приводить же везде простыню кода. Это сообщение отредактировал(а) Thunderbolt - 19.9.2011, 14:00 --------------------
Карпов Андрей, DevRel в PVS-Studio. |
|||
|
||||
volatile |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 37 Всего: 85 |
Ну раз вы отобрали только очевидные факты, то имхо, и должны предоставить достаточное основание в ошибочности. Ибо иначе, можно вообще постить каждую строку любого кода, и говорить что оно "ошибочно", но почему сам знаю. В этом коде я, например, не вижу ничего ненормального, разве что лишние скобки. Не мудрено что
Подозреваю, что в этом вагоне нужно будет копаться несчастному разработчику, терять уйму времени, чтобы убедиться что все нормально... |
|||
|
||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |