Учитывая следующий код C++:
inline auto v = []{ return 1; }();
Контекст типа замыкания является инициализатором встроенной переменной. Это приводит к использованию специального правила искажения в Itanium ABI (упомянутого здесь).
Когда libclang посещает CXXRecordDecl
, соответствующий этому типу замыкания, есть ли способ узнать, находимся ли мы в этом особом контексте?
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, известного как «libtooling», можно по существу использовать тот же язык сопоставления. Подробности описаны в урок .
В качестве альтернативы можно пройти напрямую через AST, возможно, используя РекурсивныйASTVisitor, ищем интересующие функции:
ВарДецл имеет getInit() чтобы получить инициализатор.
LambdaExpr имеет getLambdaClass() чтобы получить класс, представляющий замыкание.
CXXRecordDecl
имеет isLambda()
(и несколько связанных методов) для маркировки объявлений классов замыкания.
Однако у него нет обратного указателя на LambdaExpr
.
Используя эти методы вместе с обходом AST, который поддерживает некоторые своего рода состояние о том, где оно находится в AST, и, возможно, какое-то вспомогательное карты, можно распознать нужный узор.
Здесь, к сожалению, все становится сложнее, и, следовательно, это
лишь частичный ответ. 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
и есть тот дроид, которого я ищу. Если это не сработает, я приму ваш ответ.
@Дориан Дориан Вы правы, эта функция должна делать то, что вы хотите (в этом примере она делает). Я изначально неправильно понял вопрос, извините!
Оказывается, в 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()) [...]
Ищите объявление
v
вместо лямбды, чтобы сначала получить контекст?