Я использую libclang для анализа некоторого кода и хочу найти вызовы определенной функции и типы ее аргументов.
Например, предположим, что код:
void foo(int a, ...) {}
enum test {
ENUM_VAL1,
ENUM_VAL2
};
int main() {
enum test e = ENUM_VAL1;
int a = 1;
foo(a, e);
}
В этом случае я хочу найти функцию «foo» и увидеть, что она имеет два аргумента: первый — целое число, а второй — «проверка перечисления».
Запуск следующего кода:
static enum CXChildVisitResult visitFuncCalls(CXCursor current_cursor,
CXCursor parent,
CXClientData client_data) {
if (clang_getCursorKind(current_cursor) != CXCursor_CallExpr) {
return CXChildVisit_Recurse;
}
static const char *FUNCTION_NAME = "foo";
const CXString spelling = clang_getCursorSpelling(current_cursor);
if (strcmp(clang_getCString(spelling), FUNCTION_NAME) != 0) {
return CXChildVisit_Recurse;
}
clang_disposeString(spelling);
for (int i = 0; i < clang_Cursor_getNumArguments(current_cursor); i++) {
CXCursor argument = clang_Cursor_getArgument(current_cursor, i);
CXType argument_type = clang_getCursorType(argument);
CXString argument_type_spelling = clang_getTypeSpelling(argument_type);
printf("Argument %d: %s\n", i, clang_getCString(argument_type_spelling));
clang_disposeString(argument_type_spelling);
}
return CXChildVisit_Continue;
}
int main() {
const CXTranslationUnit unit = clang_parseTranslationUnit(
index, "file.c", NULL, 0, NULL, 0, CXTranslationUnit_None);
const CXCursor cursor = clang_getTranslationUnitCursor(unit);
clang_visitChildren(cursor, visitFuncCalls, NULL /* client_data*/);
}
Я получил:
Argument 0: int
Argument 1: unsigned int
Итак, по сути, компилятор игнорирует тот факт, что этот тип является перечислением, и показывает его как беззнаковое целое число. Есть ли способ узнать, что этот аргумент является перечислением?





Во-первых, обратите внимание, что код в вопросе (после исправления путем объявления
index) работает в случае вызова функции, которая явно принимает
аргумент типа enum test. То есть, если мы изменим строку:
void foo(int a, ...) {}
к:
void foo(int a, enum test) {}
и переместите его под объявление enum test, затем код в
отпечатки вопросов:
Argument 0: int
Argument 1: enum test <-- what we want
Таким образом, основной вопрос заключается в том, как заставить эту работу работать, когда вызываемый абонент — функция с переменным аргументом.
Мы сможем лучше понять, что происходит, сбросив AST
оригинал file.c:
$ clang -fsyntax-only -Xclang -ast-dump file.c -fno-diagnostics-color
TranslationUnitDecl 0x56530660a508 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x56530660ad30 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x56530660aad0 '__int128'
|-TypedefDecl 0x56530660ada0 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x56530660aaf0 'unsigned __int128'
|-TypedefDecl 0x56530660b0a8 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x56530660ae80 'struct __NSConstantString_tag'
| `-Record 0x56530660adf8 '__NSConstantString_tag'
|-TypedefDecl 0x56530660b140 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x56530660b100 'char *'
| `-BuiltinType 0x56530660a5b0 'char'
|-TypedefDecl 0x56530660b438 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag[1]'
| `-ConstantArrayType 0x56530660b3e0 'struct __va_list_tag[1]' 1
| `-RecordType 0x56530660b220 'struct __va_list_tag'
| `-Record 0x56530660b198 '__va_list_tag'
|-FunctionDecl 0x565306666930 <file.c:1:1, col:23> col:6 used foo 'void (int, ...)'
| |-ParmVarDecl 0x565306666860 <col:10, col:14> col:14 a 'int'
| `-CompoundStmt 0x565306666a28 <col:22, col:23>
|-EnumDecl 0x565306666a38 <line:3:1, line:6:1> line:3:6 test
| |-EnumConstantDecl 0x565306666b00 <line:4:3> col:3 referenced ENUM_VAL1 'int'
| `-EnumConstantDecl 0x565306666b50 <line:5:3> col:3 ENUM_VAL2 'int'
`-FunctionDecl 0x565306666bf0 <line:8:1, line:12:1> line:8:5 main 'int ()'
`-CompoundStmt 0x565306666f78 <col:12, line:12:1>
|-DeclStmt 0x565306666d90 <line:9:3, col:26>
| `-VarDecl 0x565306666cf0 <col:3, col:17> col:13 used e 'enum test':'enum test' cinit
| `-ImplicitCastExpr 0x565306666d78 <col:17> 'enum test':'enum test' <IntegralCast>
| `-DeclRefExpr 0x565306666d58 <col:17> 'int' EnumConstant 0x565306666b00 'ENUM_VAL1' 'int'
|-DeclStmt 0x565306666e48 <line:10:3, col:12>
| `-VarDecl 0x565306666dc0 <col:3, col:11> col:7 used a 'int' cinit
| `-IntegerLiteral 0x565306666e28 <col:11> 'int' 1
`-CallExpr 0x565306666f00 <line:11:3, col:11> 'void'
|-ImplicitCastExpr 0x565306666ee8 <col:3> 'void (*)(int, ...)' <FunctionToPointerDecay>
| `-DeclRefExpr 0x565306666e60 <col:3> 'void (int, ...)' Function 0x565306666930 'foo' 'void (int, ...)'
|-ImplicitCastExpr 0x565306666f30 <col:7> 'int' <LValueToRValue>
| `-DeclRefExpr 0x565306666e80 <col:7> 'int' lvalue Var 0x565306666dc0 'a' 'int'
`-ImplicitCastExpr 0x565306666f60 <col:10> 'unsigned int' <IntegralCast>
`-ImplicitCastExpr 0x565306666f48 <col:10> 'enum test':'enum test' <LValueToRValue>
`-DeclRefExpr 0x565306666ea0 <col:10> 'enum test':'enum test' lvalue Var 0x565306666cf0 'e' 'enum test':'enum test'
Обратите внимание на ключевые строки в конце:
`-ImplicitCastExpr 0x565306666f60 <col:10> 'unsigned int' <IntegralCast>
`-ImplicitCastExpr 0x565306666f48 <col:10> 'enum test':'enum test' <LValueToRValue>
`-DeclRefExpr 0x565306666ea0 <col:10> 'enum test':'enum test' lvalue Var 0x565306666cf0 'e' 'enum test':'enum test'
Происходит то, что выражение аргумента e подвергается двум неявным
преобразования, первым из которых является преобразование lvalue в rvalue, а
во-вторых, это продвижение с enum test на unsigned int. Это
второе преобразование, в результате которого тип сообщается как unsigned int
в исходном коде, потому что это правильный тип аргумента после
продвижения, предусмотренные семантикой функции с переменным аргументом
звонки.
Итак, наша цель сейчас — получить тип выражения под
ImplicitCastExpr узел.
ImplicitCastExprВ C++ API пропуск ImplicitCastExpr это легко, так как ты просто звонишь CastExpr::getSubExpr.
Но в C API ImplicitCastExpr, к сожалению, только указывается.
по типу курсора CXCursor_UnexposedExpr, узнаваемому напрямую
или с
clang_isUnexpose.
Следовательно, мы должны проверить это и предположить, что это означает
ImplicitCastExpr. В целом это небезопасно, поскольку другие виды
узлы также сопоставляются с CXCursor_UnexposedExpr, но в контексте
уже распознав выражение вызова аргумента функции в C
язык, я думаю ImplicitCastExpr это единственная возможность.
(К сожалению, C API часто бывает неоднозначным в таких случаях:
требующие различных хрупких эвристик для преодоления. Я рекомендую использовать
Вместо этого C++ API, если это возможно.)
Учитывая CXCursor к такому выражению, вот код, который будет искать
дерево для первого узла, который не является нераскрытым типом, и доходность
его тип:
// Client data for `getUnderTypeVisitor`.
typedef struct GetUnderTypeData {
// Underlying type, if any.
CXType underType;
// True if we find a type to use.
bool found;
} GetUnderTypeData;
// Visitor for `getUnderType`.
enum CXChildVisitResult getUnderTypeVisitor(
CXCursor c, CXCursor parent, CXClientData client_data)
{
GetUnderTypeData *data = (GetUnderTypeData *)client_data;
enum CXCursorKind kind = clang_getCursorKind(c);
// The AST node `ImplicitCastExpr` is surfaced in the C API as an
// "unexposed" kind. So if we see an unexposed kind, assume that it
// means `ImplicitCastExpr` and recursively search the children.
if (clang_isUnexposed(kind)) {
return CXChildVisit_Recurse;
}
// For any other kind, we probably have a usable type.
else {
data->underType = clang_getCursorType(c);
data->found = true;
return CXChildVisit_Break;
}
}
// Try to get the type of `c` after skipping any `ImplicitCastExpr`
// nodes. Return true and set `*underType` if we can, and return false
// otherwise.
bool getUnderType(CXCursor c, CXType * /*OUT*/ underType)
{
GetUnderTypeData data;
data.found = false;
clang_visitChildren(c, getUnderTypeVisitor, &data);
if (data.found) {
*underType = data.underType;
return true;
}
else {
return false;
}
}
Вставка приведенного выше кода в исходный код вопроса (плюс пара другие исправления), имеем:
// ---------------------------- BEGIN ADDED ----------------------------
#include <clang-c/Index.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// Client data for `getUnderTypeVisitor`.
typedef struct GetUnderTypeData {
// Underlying type, if any.
CXType underType;
// True if we find a type to use.
bool found;
} GetUnderTypeData;
// Visitor for `getUnderType`.
enum CXChildVisitResult getUnderTypeVisitor(
CXCursor c, CXCursor parent, CXClientData client_data)
{
GetUnderTypeData *data = (GetUnderTypeData *)client_data;
enum CXCursorKind kind = clang_getCursorKind(c);
// The AST node `ImplicitCastExpr` is surfaced in the C API as an
// "unexposed" kind. So if we see an unexposed kind, assume that it
// means `ImplicitCastExpr` and recursively search the children.
if (clang_isUnexposed(kind)) {
return CXChildVisit_Recurse;
}
// For any other kind, we probably have a usable type.
else {
data->underType = clang_getCursorType(c);
data->found = true;
return CXChildVisit_Break;
}
}
// Try to get the type of `c` after skipping any `ImplicitCastExpr`
// nodes. Return true and set `*underType` if we can, and return false
// otherwise.
bool getUnderType(CXCursor c, CXType * /*OUT*/ underType)
{
GetUnderTypeData data;
data.found = false;
clang_visitChildren(c, getUnderTypeVisitor, &data);
if (data.found) {
*underType = data.underType;
return true;
}
else {
return false;
}
}
// ----------------------------- END ADDED -----------------------------
static enum CXChildVisitResult visitFuncCalls(CXCursor current_cursor,
CXCursor parent,
CXClientData client_data) {
if (clang_getCursorKind(current_cursor) != CXCursor_CallExpr) {
return CXChildVisit_Recurse;
}
static const char *FUNCTION_NAME = "foo";
const CXString spelling = clang_getCursorSpelling(current_cursor);
if (strcmp(clang_getCString(spelling), FUNCTION_NAME) != 0) {
return CXChildVisit_Recurse;
}
clang_disposeString(spelling);
for (int i = 0; i < clang_Cursor_getNumArguments(current_cursor); i++) {
CXCursor argument = clang_Cursor_getArgument(current_cursor, i);
CXType argument_type = clang_getCursorType(argument);
CXString argument_type_spelling = clang_getTypeSpelling(argument_type);
printf("Argument %d: %s\n", i, clang_getCString(argument_type_spelling));
// -------------------------- BEGIN ADDED --------------------------
CXType underType;
if (getUnderType(argument, &underType)) {
CXString underTypeSpelling = clang_getTypeSpelling(underType);
printf("underType: %s\n", clang_getCString(underTypeSpelling));
clang_disposeString(underTypeSpelling);
}
// --------------------------- END ADDED ---------------------------
clang_disposeString(argument_type_spelling);
}
return CXChildVisit_Continue;
}
int main() {
// --------------------------- BEGIN ADDED ---------------------------
CXIndex index = clang_createIndex(0, 0);
// ---------------------------- END ADDED ----------------------------
const CXTranslationUnit unit = clang_parseTranslationUnit(
index, "file.c", NULL, 0, NULL, 0, CXTranslationUnit_None);
const CXCursor cursor = clang_getTranslationUnitCursor(unit);
clang_visitChildren(cursor, visitFuncCalls, NULL /* client_data*/);
}
При запуске на оригинале file.c результат будет такой:
Argument 0: int
underType: int
Argument 1: unsigned int
underType: enum test <--- got it
Обновление. В приведенном выше коде есть две ошибки:
getUnderType должен проверить, может ли c сам дать тип.
Он неправильно обрабатывает случай, когда перечислитель передается непосредственно в качестве аргумента.
См. обновленный ответ на вопрос Как мне получить перечислимый тип clang::EnumConstantDecl? для исправления этих проблем.