Получить тип перечисления аргумента вызова функции с помощью libclang

Я использую 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

Итак, по сути, компилятор игнорирует тот факт, что этот тип является перечислением, и показывает его как беззнаковое целое число. Есть ли способ узнать, что этот аргумент является перечислением?

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

Ответы 1

Ответ принят как подходящий

Проблема в переменных аргументах

Во-первых, обратите внимание, что код в вопросе (после исправления путем объявления 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

Обновление. В приведенном выше коде есть две ошибки:

  1. getUnderType должен проверить, может ли c сам дать тип.

  2. Он неправильно обрабатывает случай, когда перечислитель передается непосредственно в качестве аргумента.

См. обновленный ответ на вопрос Как мне получить перечислимый тип clang::EnumConstantDecl? для исправления этих проблем.

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