Есть ли способ узнать, был ли введен тип замыкания в контексте инициализации встроенной переменной с помощью libclang?

Учитывая следующий код C++:

inline auto v = []{ return 1; }();

Контекст типа замыкания является инициализатором встроенной переменной. Это приводит к использованию специального правила искажения в Itanium ABI (упомянутого здесь).

Когда libclang посещает CXXRecordDecl, соответствующий этому типу замыкания, есть ли способ узнать, находимся ли мы в этом особом контексте?

Ищите объявление v вместо лямбды, чтобы сначала получить контекст?

Jarod42 20.06.2024 12:43
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте CXXRecordDecl::getLambdaContextDecl

Поскольку вопрос конкретно касается ситуации, когда на изменение имени Itanium ABI влияет лямбда-контекст, ответом является CXXRecordDecl::getLambdaContextDecl(). Это вернет переменную контекста в соответствующих контекстах. Однако для этого необходимо использовать API C++, поскольку он не представлен в API C. (Спасибо ОП за указание на это в комментарии. Я упустил из виду его актуальность.)

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


Как выглядит АСТ?

Для начала давайте посмотрим на AST для ввода:

inline auto v = []{ return 1; }();

Запустите команду clang++ -fsyntax-only -Xclang -ast-dump test.cc. вывод будет примерно таким:

TranslationUnitDecl 0x56461ab90ec8 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x56461ab91730 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x56461ab91490 '__int128'
|-TypedefDecl 0x56461ab917a0 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x56461ab914b0 'unsigned __int128'
|-TypedefDecl 0x56461ab91b18 <<invalid sloc>> <invalid sloc> implicit __NSConstantString '__NSConstantString_tag'
| `-RecordType 0x56461ab91890 '__NSConstantString_tag'
|   `-CXXRecord 0x56461ab917f8 '__NSConstantString_tag'
|-TypedefDecl 0x56461ab91bb0 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x56461ab91b70 'char *'
|   `-BuiltinType 0x56461ab90f70 'char'
|-TypedefDecl 0x56461abd7dd8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag[1]'
| `-ConstantArrayType 0x56461abd7d80 '__va_list_tag[1]' 1 
|   `-RecordType 0x56461ab91ca0 '__va_list_tag'
|     `-CXXRecord 0x56461ab91c08 '__va_list_tag'
|-VarDecl 0x56461abd7ea0 <test.cc:4:1, col:33> col:13 v 'int':'int' inline cinit
| `-ExprWithCleanups 0x56461abd8b50 <col:17, col:33> 'int':'int'
|   `-CXXOperatorCallExpr 0x56461abd8978 <col:17, col:33> 'int':'int' '()'
|     |-ImplicitCastExpr 0x56461abd8908 <col:32, col:33> 'auto (*)() const -> int' <FunctionToPointerDecay>
|     | `-DeclRefExpr 0x56461abd8888 <col:32, col:33> 'auto () const -> int' lvalue CXXMethod 0x56461abd8140 'operator()' 'auto () const -> int'
|     `-ImplicitCastExpr 0x56461abd8960 <col:17, col:31> 'const (lambda at test.cc:4:17)' lvalue <NoOp>
|       `-MaterializeTemporaryExpr 0x56461abd8948 <col:17, col:31> '(lambda at test.cc:4:17)' lvalue
|         `-LambdaExpr 0x56461abd8720 <col:17, col:31> '(lambda at test.cc:4:17)'
|           |-CXXRecordDecl 0x56461abd7ff8 <col:17> col:17 implicit class definition
|           | |-DefinitionData lambda pass_in_registers empty standard_layout trivially_copyable literal can_const_default_init
|           | | |-DefaultConstructor defaulted_is_constexpr
|           | | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
|           | | |-MoveConstructor exists simple trivial needs_implicit
|           | | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param
|           | | |-MoveAssignment
|           | | `-Destructor simple irrelevant trivial
|           | |-CXXMethodDecl 0x56461abd8140 <col:18, col:31> col:17 used constexpr operator() 'auto () const -> int' inline
|           | | `-CompoundStmt 0x56461abd8398 <col:19, col:31>
|           | |   `-ReturnStmt 0x56461abd8388 <col:21, col:28>
|           | |     `-IntegerLiteral 0x56461abd81f8 <col:28> 'int' 1
|           | |-CXXConversionDecl 0x56461abd85a8 <col:17, col:31> col:17 implicit constexpr operator int (*)() 'auto (*() const noexcept)() -> int' inline
|           | |-CXXMethodDecl 0x56461abd8660 <col:17, col:31> col:17 implicit __invoke 'auto () -> int' static inline
|           | `-CXXDestructorDecl 0x56461abd8748 <col:17> col:17 implicit referenced ~(lambda at test.cc:4:17) 'void () noexcept' inline default trivial
|           `-CompoundStmt 0x56461abd8398 <col:19, col:31>
|             `-ReturnStmt 0x56461abd8388 <col:21, col:28>
|               `-IntegerLiteral 0x56461abd81f8 <col:28> 'int' 1
`-CXXRecordDecl 0x56461abd7ff8 <col:17> col:17 implicit class definition
  |-DefinitionData lambda pass_in_registers empty standard_layout trivially_copyable literal can_const_default_init
  | |-DefaultConstructor defaulted_is_constexpr
  | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveConstructor exists simple trivial needs_implicit
  | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveAssignment
  | `-Destructor simple irrelevant trivial
  |-CXXMethodDecl 0x56461abd8140 <col:18, col:31> col:17 used constexpr operator() 'auto () const -> int' inline
  | `-CompoundStmt 0x56461abd8398 <col:19, col:31>
  |   `-ReturnStmt 0x56461abd8388 <col:21, col:28>
  |     `-IntegerLiteral 0x56461abd81f8 <col:28> 'int' 1
  |-CXXConversionDecl 0x56461abd85a8 <col:17, col:31> col:17 implicit constexpr operator int (*)() 'auto (*() const noexcept)() -> int' inline
  |-CXXMethodDecl 0x56461abd8660 <col:17, col:31> col:17 implicit __invoke 'auto () -> int' static inline
  `-CXXDestructorDecl 0x56461abd8748 <col:17> col:17 implicit referenced ~(lambda at test.cc:4:17) 'void () noexcept' inline default trivial

Нас интересует узел CXXRecordDecl. В выводе выше такой узел только один по адресу 0x56461abd7ff8, хотя он появляется дважды из-за особенностей работы -ast-dump. (По сути, есть недеревянное ребро, и -ast-dump это не компенсирует.)

Цепочка, представляющая наибольший интерес:

|-VarDecl 0x56461abd7ea0 <test.cc:4:1, col:33> col:13 v 'int':'int' inline cinit
[...]
|         `-LambdaExpr 0x56461abd8720 <col:17, col:31> '(lambda at test.cc:4:17)'
|           |-CXXRecordDecl 0x56461abd7ff8 <col:17> col:17 implicit class definition

То есть наш CXXRecordDecl является дочерним элементом LambdaExpr, который является потомок VarDecl, представляющего декларацию v. тогда цель состоит в том, чтобы распознать эту закономерность в AST.

Признаемся с clang-query

clang-запрос это инструмент командной строки, который использует Язык AST Matcher найти интересующие элементы. Следующий сценарий оболочки имеет AST сопоставитель, который распознает искомый шаблон:

#!/bin/sh

PATH=$HOME/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH

query='m

  varDecl(
    hasInitializer(
      expr(
        hasDescendant(
          lambdaExpr(
            hasDescendant(
              cxxRecordDecl().bind("lambdaRecord")
            )
          ).bind("lambda")
        )
      )
    )
  ).bind("decl")

'

if [ "x$1" = "x" ]; then
  echo "usage: $0 filename.cc -- <compile options like -I, etc.>"
  exit 2
fi

# Run the query.  Setting 'bind-root' to false means clang-query will
# not also print a redundant "root" binding.
clang-query \
  -c = "set bind-root false" \
  -c = "$query" \
  "$@"

# EOF

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

$ ./cmd.sh test.cc --

Match #1:

$PWD/test.cc:4:1: note: "decl" binds here
inline auto v = []{ return 1; }();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$PWD/test.cc:4:17: note: "lambda" binds here
inline auto v = []{ return 1; }();
                ^~~~~~~~~~~~~~~
$PWD/test.cc:4:17: note: "lambdaRecord" binds here
inline auto v = []{ return 1; }();
                ^
1 match.

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

Распознавание с помощью C++ API

При использовании C++ API, известного как «libtooling», можно по существу использовать тот же язык сопоставления. Подробности описаны в урок .

В качестве альтернативы можно пройти напрямую через AST, возможно, используя РекурсивныйASTVisitor, ищем интересующие функции:

  • ВарДецл имеет getInit() чтобы получить инициализатор.

  • LambdaExpr имеет getLambdaClass() чтобы получить класс, представляющий замыкание.

  • CXXRecordDecl имеет isLambda() (и несколько связанных методов) для маркировки объявлений классов замыкания. Однако у него нет обратного указателя на LambdaExpr.

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

Распознавание с помощью C API

Здесь, к сожалению, все становится сложнее, и, следовательно, это лишь частичный ответ. C API предоставляет лишь небольшую, своеобразную часть того, что доступно в C++ API, и делает это в неоднозначный путь. Вместо таких вещей, как VarDecl::getInit() и LambdaExpr::getLambdaClass(), есть только clang_visitChildren, который посещает всех детей, но не делает различий между ними по роль. Инициализатор VarDecl может быть первым дочерним элементом в простой сценарий, но пятый ребенок в более сложном примере ( типы, используемые в спецификаторе типа и деклараторе, также могут быть дочерними, если они достаточно «интересны»), и то же самое для LambdaExpr. Это «последняя капля», по которой я в конце концов отказался от использования C. API: в определенный момент эвристический вывод о роли детей просто становится неустойчиво хрупким.

Кроме того, CXXRecordDecl::isLambda не отображается. Единственный метод доступа, специфичный для CXXRecordDecl, clang_CXXRecord_isAbstract(), соответствующий CXXRecordDecl::isAbstract(). Если вы хотите узнать какие-либо другие подробности (кроме перечисления детей), не повезло тебе.

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

Если вышеизложенное недостаточно точно, альтернативой является C++ API. Я бы порекомендовал. Намного менее приятно развиваться против него из-за очень медленное время перекомпиляции (20 с на файл вместо 0,2 с), но оно имеет почти все, что вы хотели бы знать, и достаточно хорошо организовано.

Для полноты отмечу, что python libclang — это просто Python оболочка вокруг C API, поэтому имеет все те же проблемы.

Спасибо за Ваш ответ. Кажется, getLambdaContextDecl из CXXRecordDecl и есть тот дроид, которого я ищу. Если это не сработает, я приму ваш ответ.

Dorian 24.06.2024 14:13

@Дориан Дориан Вы правы, эта функция должна делать то, что вы хотите (в этом примере она делает). Я изначально неправильно понял вопрос, извините!

Scott McPeak 24.06.2024 19:48
Ответ принят как подходящий

Оказывается, в libtooling есть специальная функция CXXRecordDecl::getLambdaContextDecl, которую можно использовать для решения проблемы:

if (decl->isLambda()) {
    const clang::Decl *context = getLambdaContextDecl(decl);
    if (const auto *varDecl =
            llvm::dyn_cast_or_null<const clang::VarDecl>(context))
    {
        if (varDecl->isInline()) [...]

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