парсинг — парсинг C ++ и управление потоком

Eсть огромный C ++ проект, построенный с использованием CMake и gcc 4.2.3. Приложение использует несколько процессов.

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

Я обнаружил, что в некоторых main.cpp (файл, где все начинается) есть ловить выражение, в котором происходит запись в файл. Так что мне нужно найти бросать выражения, которые удовлетворяют следующим критериям:

  1. Один из определенных типов ошибок, используемых в выражении броска (например, runtime_error, logic_error и т. Д.).
  2. Там нет другого ловить в стеке между ловить находится в main.cpp и бросать выражение. Если есть ловить, он может добавить дополнительную информацию (что важно) и перебросить. Более того, он может перебрасываться с использованием другого типа ошибки или даже молчать.

Проект очень большой, и трудно сказать, будет ли эта часть кода когда-либо выполняться в этой сборке. Некоторые сборки используют определенные библиотеки, а другие нет.

Может быть, я не прав с подходом, но я думаю, что решение состоит из двух этапов:

  1. Анализируйте весь код C ++ так, как его видит компилятор (чтобы убедиться, что throw не находится в разделе комментариев, не является макросом и т. Д.)
  2. Найти все выражения броска в скомпилированном дереве и эмулировать бросок. На самом деле, я вижу проблему здесь, потому что условия могут быть действительно вовлечены, например:

    string error_msg;
    enum Condition condition;
    switch(condition)
    {
    CONDITION1: error_msg = "sadasda"; break;
    CONDITION2: error_msg = "sadasds1111a"; break;
    CONDITION3: error_msg = "sasdasadasda"; break;
    default: error_msg = "sadasda"; break;
    }
    throw logic_error(error_msg);
    

Может быть, это все неправильно, и следует использовать другой подход. Буду рад видеть ваш совет.

2

Решение

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

По сути, вы хотите повторно использовать существующий парсер для ваших целей, что тоже непросто. Вам нужно будет изучить различные плагины компилятора и инструменты статического анализа. Например, лязг статический анализатор кажется (относительно!) легко расширяемым. Возможно, более простым способом было бы использовать существующий статический анализатор C ++, например корпия, и обнаружить неисследованные исключения. Затем вы модифицируете свой main, чтобы перестать перехватывать интересующие вас исключения и просматривать список необработанных исключений. Вы далеко не закончили, но вы можете начать работать оттуда. C ++ lint — это не свободное программное обеспечение, а бесплатные альтернативы AFAIK (cppcheck, Clang Anlyzer) не имеют расширенного анализа исключений. Может быть Coverity Также может представлять интерес, у них есть скрипты и / или SDK для написания расширений.

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

В любом случае, я желаю вам удачи, работа с большими кодами никогда не бывает легкой;)

2

Другие решения

(Отвечая задолго до вопроса; недавнее редактирование этого вопроса сделало этот вопрос доступным для меня).

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

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

Тогда есть немного о отслеживании «бросков». Вам нужен поток управления внутри каждой функции / метода, чтобы следовать броскам в метод, а затем вам нужно отслеживать броски через вызовы методов. Для последнего вам нужен точный график звонков. Стандартный компилятор может предоставить поток управления внутри метода, но он не будет вычислять глобальный граф вызовов.

Чтобы получить хороший график звонков,
вам нужно разрешить явные вызовы из foo в bar, и вам нужно определить для косвенных вызовов через указатели, какие методы / функции являются возможными целями вызова, и вам нужно определить для полиморфных вызовов методов (частный случай косвенных вызовов) то же самое. Так что вам нужен анализатор очков.

Благодаря локальному потоку управления и точному графику вызовов вы можете теперь находить каждый начальный бросок и отслеживать («имитировать») их с сайта броска через цепочки захвата, чтобы увидеть, достигают ли они в конечном итоге основного (или, по крайней мере, при вызове функция регистрации). Метание броска-улова-испытания-повторения довольно просто для отслеживания; у вас будут проблемы в сложном предложении catch, содержащем много логики, которая в конечном итоге перебрасывает, отслеживая реальное выброшенное исключение или даже когда что-то перебрасывается. Добро пожаловать в статический анализ и смолу Тьюринга.

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

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

лязг может предоставить часть этого; он, безусловно, будет анализировать и создавать AST для C ++. После запуска LLVM у вас будет анализ потока управления внутри метода. Я думаю, что Clang можно настроить для анализа нескольких модулей компиляции, так что это большой шаг вперед по сравнению с тем, что предлагает вам использование компилятора. Я не знаю, что предлагает Clang для анализа точек или построения графиков вызовов. Вам нужно будет это заполнить и создать собственный код для «имитации» бросков.

наш DMS Software Reengineering Toolkit, используется для анализа и преобразования программ, может быть использовано для этого.
DMS также может точно анализировать полный C ++ компилятором и предназначен для одновременного анализа / обработки нескольких модулей компиляции.

DMS действительно производит анализ потока управления внутри метода, и у него есть анализ потока данных внутри метода. В настоящее время у нас нет анализа точек для C ++, но в DMS есть как анализ точек, так и построение графов вызовов для C, который можно было бы использовать для тестирования, который был протестирован в приложениях с 15 000 (не опечаткой) компиляцией. единицы в одном изображении, имеющие около 50 000 функций и косвенные вызовы, запутанные во всем этом. (Если у Clang нет такого механизма, это огромная разница в стартовых местах). С этим, тогда вы можете построить симуляцию броска на вершине.

Принимая во внимание все это, я думаю, что работа над Clang и / или DMS вышеупомянутого важна. Если ваше приложение содержит менее миллиона строк, я ожидаю, что вы сделаете это быстрее (если не более небрежно), просто отыскивая предложения throw с использованием grep и отслеживая их вручную в коде. Вы сказали, что ваше приложение было огромным; Трудно сказать, что это на самом деле означает без конкретных цифр. Эти инструменты действительно хорошо работают в масштабе, но не стоят усилий, когда ваша проблема небольшая. [Что интересно, так это то, что граница для «маленьких» перемещается со временем, когда инструменты становятся лучше].

1