Обнаружение мертвого кода в устаревшем проекте C/C++

Как бы вы пошли об обнаружении мертвого кода в коде C/C++? У меня достаточно большая кодовая база для работы, и по крайней мере 10-15% - мертвый код. Есть ли какой-нибудь инструмент на основе Unix, чтобы идентифицировать эти области? Некоторые фрагменты кода все еще используют много препроцессора, может ли автоматизированный процесс справиться с этим?

8 ответов

Решение

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

Популярным инструментом для gcc toolchain является gcov вместе с графическим интерфейсом lcov ( http://ltp.sourceforge.net/coverage/lcov.php).

Если вы используете gcc, вы можете скомпилировать с поддержкой gcov, которая включается флагом --coverage. Затем запустите ваше приложение или запустите тестовый набор с этой сборкой с поддержкой gcov.

В основном, gcc будет генерировать некоторые дополнительные файлы во время компиляции, и приложение также будет генерировать некоторые данные покрытия во время работы. Вы должны собрать все это (файлы.gcdo и.gcda). Я не буду вдаваться в подробности, но вам, вероятно, нужно установить две переменные окружения, чтобы собирать данные покрытия разумным способом: GCOV_PREFIX и GCOV_PREFIX_STRIP...

После запуска вы можете собрать все данные покрытия и запустить их через lcov toolsuite. Возможно также объединение всех файлов покрытия из разных тестовых прогонов, хотя и немного сложное.

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

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

Скомпилируйте его в gcc с -Wunreachable-code.

Я думаю, что чем более свежая версия, тем лучше будут результаты, но, возможно, я ошибаюсь, полагая, что над этим они активно работают. Обратите внимание, что это делает анализ потока, но я не верю, что он говорит о "коде", который уже мертв к моменту выхода из препроцессора, потому что он никогда не анализируется компилятором. Он также не будет обнаруживать, например, экспортированные функции, которые никогда не вызываются, или специальный код обработки кода, который просто оказывается невозможным, потому что ничто никогда не вызывает функцию с этим параметром - для этого вам нужно покрытие кода (и запускать функциональные тесты, а не модульные тесты. Предполагается, что модульные тесты имеют 100% покрытие кода и, следовательно, выполняют пути к коду, которые являются "мертвыми" для приложения). Тем не менее, учитывая эти ограничения, это простой способ начать поиск наиболее полностью запутанных подпрограмм в базе кода.

В этой рекомендации CERT перечислены некоторые другие инструменты для обнаружения статического мертвого кода.

Только для кода C и при условии, что исходный код всего проекта доступен, запустите анализ с помощью инструмента с открытым исходным кодом Frama-C. Любой оператор программы, отображающий красный цвет в графическом интерфейсе, является мертвым кодом.

Если у вас есть проблемы с "мертвым кодом", вас также может заинтересовать удаление "лишнего кода", кода, который выполняется, но не влияет на конечный результат. Это требует от вас точного моделирования функций ввода / вывода (вы не хотите удалять вычисления, которые кажутся "запасными", но которые используются в качестве аргумента для printf). Frama-C имеет возможность указать запасной код.

У Mozilla и Open Office есть собственные решения.

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

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

Я нашел страницу со статическим анализом исходного кода, на которой также перечислены многие другие инструменты.

Если вам это тоже не поможет, и вы особенно заинтересованы в поиске мертвого кода, связанного с препроцессором, я бы порекомендовал вам опубликовать более подробную информацию о коде. Например, если это в основном связано с различными комбинациями настроек #ifdef, вы можете написать сценарии для определения настроек (комбинаций) и выяснения, какие комбинации на самом деле никогда не создавались и т. Д.

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

Мы использовали наш DMS Software Reengineering Toolkit для реализации именно этого для кода Java, анализируя все задействованные модули компиляции, создавая таблицы символов для всего и отыскивая все ссылки. Определение верхнего уровня без ссылок и без претензий на то, что он является внешним элементом API, устарело. Этот инструмент также автоматически удаляет мертвый код, и в конце вы можете выбрать, что вы хотите: отчет о мертвых объектах или код, удаленный из этих объектов.

DMS также анализирует C++ на множестве диалектов (EDIT, февраль 2014 года: включая версии MS и GCC для C++14 [EDIT, ноябрь 2017: теперь C++ 17]) и создает все необходимые таблицы символов. Отслеживание мертвых ссылок было бы простым с этого момента. DMS также может быть использован для их удаления. См. http://www.semanticdesigns.com/Products/DMS/DMSToolkit.html

g++ 4.01 -Wunreachable-code предупреждает о коде, который недоступен внутри функции, но не предупреждает о неиспользуемых функциях.

int foo() { 
    return 21; // point a
}

int bar() {
  int a = 7;
  return a;
  a += 9;  // point b
  return a;
}

int main(int, char **) {
    return bar();
}

g ++ 4.01 выдаст предупреждение о точке b, но ничего не скажет о foo() (точка a), даже если он недоступен в этом файле. Это поведение правильное, хотя и разочаровывающее, потому что компилятор не может знать, что функция foo () не объявлена ​​extern в каком-либо другом модуле компиляции и вызвана оттуда; только линкер может быть уверен.

Инструмент покрытия яблочко поможет. Это не бесплатно, хотя.

Другие вопросы по тегам