Как исключить заголовки из AST в clang?

Я генерирую AST, используя Clang. У меня есть следующий файл (lambda.cpp) для анализа:

#include <iostream>

void my_lambda()
{
    auto lambda = [](auto x, auto y) {return x + y;};
    std::cout << "fabricati diem"; 
}

Я анализирую это с помощью следующей команды:

clang -Xclang -ast-dump -fsyntax-only lambda.cpp

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

Как исключить заголовки при генерации AST?

5 ответов

Решение

clang-check может быть полезным по этому вопросу, clang-check имеет опцию -ast-dump-filter=<string> задокументировано как следовать

-ast-dump-filter = - Использовать с -ast-dump или -ast-print для вывода / печати только узлов объявления AST, имеющих определенную подстроку в определенном имени. Используйте -ast-list для получения списка всех фильтруемых имен узлов объявлений.

когда clang-check бежать с -ast-dump-filter=my_lambda в примере кода (lambda.cpp)

#include <iostream>

void my_lambda()
{
    auto lambda = [](auto x, auto y) {return x + y;};
    std::cout << "fabricati diem"; 
}

Сбрасывает только соответствующий узел объявления FunctionDecl my_lambda 'void (void)'

Вот аргументы командной строки и несколько строк из вывода.

$ clang-check -extra-arg=-std=c++1y -ast-dump -ast-dump-filter=my_lambda lambda.cpp --

FunctionDecl 0x2ddf630 <lambda.cpp:3:1, line:7:1> line:3:6 my_lambda 'void (void)'
`-CompoundStmt 0x2de1558 <line:4:1, line:7:1>
  |-DeclStmt 0x2de0960 <line:5:9, col:57>

Фильтрация по определенному идентификатору в порядке, используя -ast-dump-filter, Но что, если вы хотите использовать ast из всех идентификаторов в одном файле?

Я придумал следующее решение:

Добавьте одну узнаваемую строку после включения:

#include <iostream>
int XX_MARKER_XX = 123234; // marker line for ast-dump
void my_lambda()
...

Затем свалить аст с

clang-check -extra-arg=-std=c++1y -ast-dump lambda.cpp > ast.txt

Вы можете легко вырезать все вещи, прежде чем XX_MARKER_XX прочь с sed:

cat ast.txt | sed -n '/XX_MARKER_XX/,$p'  | less

Еще много, но гораздо полезнее с большими файлами.

Я столкнулся с той же проблемой. Мой контекст заключается в том, что мне нужно разобрать AST в формате JSON, и я хотел бы избавиться от всех заголовков и ненужных файлов. Я попытался воспроизвести ответ @textshell ( /questions/13928569/kak-isklyuchit-zagolovki-iz-ast-v-clang/59020211#59020211), но заметил, что CLANG ведет себя по-другому в моем случае. Версия CLANG, которую я использую:

      $ clang --version                                             
Debian clang version 13.0.1-+rc1-1~exp4
Target: x86_64-pc-linux-gnu
Thread model: posix

Чтобы объяснить мой случай, давайте рассмотрим следующий пример:

Оба и mainявляются функциями из одного и того же исходного файла ( function_definition_invocation.c ). Однако он указывается только в FunctionDeclузел my_function. Я предполагаю, что такое поведение связано с тем, что обе функции принадлежат одному и тому же файлу, а CLANG печатает местоположение файла только в принадлежащем ему узле.

Как только будет найдено первое вхождение основного файла, каждый последующий узел должен быть добавлен в результирующий отфильтрованный файл JSON. Код, который я использую:

      def filter_ast_only_source_file(source_file, json_ast):
    
    new_inner = []
    first_occurrence_of_main_file = False
    for entry in json_ast['inner']:
        if not first_occurrence_of_main_file:
            if entry.get('isImplicit', False):
                continue

            file_name = None
            loc = entry.get('loc', {})
            if 'file' in loc:
                file_name = loc['file']

            if 'expansionLoc' in loc:
                if 'file' in loc['expansionLoc']:
                    file_name = loc['expansionLoc']['file']

            if file_name != source_file:
                continue

            new_inner.append(entry)
            first_occurrence_of_main_file = True
        else:
            new_inner.append(entry)

    json_ast['inner'] = new_inner

И я называю это так:

      generated_ast = subprocess.run(["clang", "-Xclang", "-ast-dump=json", source_file], capture_output=True) # Output is in bytes. In case it's needed, decode it to get string
# Parse the output into a JSON object
json_ast = json.loads(generated_ast.stdout)
filter_ast_only_source_file(source_file, json_ast)

Пока вроде работает.

Это проблема с C++, а не clang: в C++ нет файлов, есть только модуль компиляции. Когда ты #include файл, в который вы включаете все определения в указанном файле (рекурсивно) в ваш модуль компиляции, и их невозможно дифференцировать (это то, что стандарт ожидает от вашего компилятора).

Представьте себе другой сценарий:

/////////////////////////////
// headertmp.h
#if defined(A)
    struct Foo {
        int bar;
    };
#elif defined(B)
    struct Foo {
        short bar;
    };
#endif

/////////////////////////////
// foobar.cpp
#ifndef A
# define B
#endif

#include "headertmp.h"

void foobar(Foo foo) {
    // do stuff to foo.bar
}

Ваш foobar.cpp объявляет структуру с именем Foo и функция под названием foobar но headertmp.h сам по себе не определяет Foo если A или же B определены. Только в модуле компиляции foobar, где они встречаются, вы можете понять headertmp.h,

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

В выгруженном AST есть указание на исходный файл для каждого узла. Таким образом, сброшенный AST может быть отфильтрован на основе данных узлов AST второго уровня.

Вам нужно соответствовать и file в expansionLoc в locпротив имени файла верхнего уровня. Мне кажется, это прилично работает. Некоторые узлы по какой-то причине не содержат этих элементов. Узлы с isImplicit можно безопасно пропустить, но я не уверен, что происходит с другими узлами без информации об имени файла.

Следующий скрипт python фильтрует "astdump.json" в "astdump.filtered.json", используя эти правила (выполнение преобразования в потоковом режиме остается в качестве упражнения для читателя):

      #! /usr/bin/python3

import json
import sys

if len(sys.argv) != 2:
    print('Usage: ' + sys.argv[0] + ' filename')
    sys.exit(1)

filename = sys.argv[1]

with open('astdump.json', 'rb') as input, open('astdump.filtered.json', 'w') as output:
    toplevel = json.load(input)
    new_inner = []
    for o in toplevel['inner']:
        if o.get('isImplicit', False):
            continue

        file_name = None
        loc = o.get('loc', {})
        if 'file' in loc:
            file_name = loc['file']

        if 'expansionLoc' in loc:
            if 'file' in loc['expansionLoc']:
                file_name = loc['expansionLoc']['file']

        if file_name != filename:
            continue

        new_inner.append(o)

    toplevel['inner'] = new_inner
    json.dump(toplevel, output, indent=4)
Другие вопросы по тегам