Clang: как получить имя макроса, используемое для размера объявления массива постоянного размера

TL;DR;

Как получить имя макроса, используемое для размера объявления массива постоянного размера, из callExpr -> arg_0 -> DeclRefExpr.

Подробное описание проблемы:

Недавно я начал работать над задачей, которая требует инструмента преобразования исходного кода для изменения вызовов определенных функций с дополнительным аргументом. Изучение способов, которыми я могу достичь, познакомило меня с этим удивительным набором инструментов Clang. Я научился использовать различные инструменты, предоставленные в libtooling, для достижения своей цели. Но теперь я застрял в проблеме, обратитесь за помощью сюда.

Рассмотрим приведенную ниже программу (фиктивную из моих источников), моя цель - переписать все вызовы функции strcpy с помощью безопасной версии strcpy_s и добавить дополнительный параметр в новый вызов функции, то есть - максимальный размер указателя назначения. поэтому для приведенной ниже программы мой рефакторинговый вызов будет выглядеть как strcpy_s(inStr, STR_MAX, argv[1]);

Я написал класс RecursiveVisitor и проверил все вызовы функций в методе VisitCallExpr, чтобы получить максимальный размер аргумента назначения, я получаю VarDecl первого аргумента и пытаюсь получить размер (ConstArrayType). Поскольку исходный файл уже предварительно обработан, я вижу размер 2049, но в этом случае мне нужен макрос STR_MAX. как я могу это получить? (Создание замен с этой информацией и последующая замена их с помощью RefactoringTool)

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 

#define STR_MAX 2049

int main(int argc, char **argv){
  char inStr[STR_MAX];

  if(argc>1){
    //Clang tool required to transaform the below call into strncpy_s(inStr, STR_MAX, argv[1], strlen(argv[1]));
    strcpy(inStr, argv[1]);
  } else {
    printf("\n not enough args");
    return -1;
  }

  printf("got [%s]", inStr);

  return 0;
}

person d3v-sci    schedule 09.06.2019    source источник


Ответы (1)


Как вы правильно заметили, исходный код уже препроцессирован и в нем развернуты все макросы. Таким образом, AST будет просто иметь целочисленное выражение в качестве размера массива.

Немного информации об источниках

ПРИМЕЧАНИЕ: вы можете пропустить его и сразу перейти к решению ниже

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

Поскольку вы ищете способ получить оригинальное имя макроса для замены, вам нужно только получить написание (т. е. то, как оно было написано в исходном исходном коде), и вам не нужно чтобы нести много информации об определениях макросов, макросах в стиле функций и их аргументах и ​​т. д.

В Clang есть два типа различных местоположений: SourceLocation и CharSourceLocation. Первую можно найти почти везде через AST. Это относится к позиции с точки зрения токенов. Это объясняет, почему позиции begin и end могут несколько противоречить интуиции:

// clang::DeclRefExpr
//
//  ┌─ begin location
foo(VeryLongButDescriptiveVariableName);
//  └─ end location
// clang::BinaryOperator
//
//           ┌─ begin location
int Result = LHS + RHS;
//                 └─ end location

Как видите, этот тип расположения источника указывает на начало соответствующего токена. С другой стороны, CharSourceLocation указывает непосредственно на символы.

Итак, чтобы получить исходный текст выражения, нам нужно преобразовать SourceLocation в CharSourceLocation и получить соответствующий текст из источника.

Решение

Я изменил ваш пример, чтобы показать и другие случаи расширения макросов:

#define STR_MAX 2049
#define BAR(X) X

int main() {
  char inStrDef[STR_MAX];
  char inStrFunc[BAR(2049)];
  char inStrFuncNested[BAR(BAR(STR_MAX))];
}

Следующий код:

// clang::VarDecl *VD;
// clang::ASTContext *Context;
auto &SM = Context->getSourceManager();
auto &LO = Context->getLangOpts();
auto DeclarationType = VD->getTypeSourceInfo()->getTypeLoc();

if (auto ArrayType = DeclarationType.getAs<ConstantArrayTypeLoc>()) {
  auto *Size = ArrayType.getSizeExpr();

  auto CharRange = Lexer::getAsCharRange(Size->getSourceRange(), SM, LO);
  // Lexer gets text for [start, end) and we want him to grab the end as well
  CharRange.setEnd(CharRange.getEnd().getLocWithOffset(1));

  auto StringRep = Lexer::getSourceText(CharRange, SM, LO);
  llvm::errs() << StringRep << "\n";
}

производит этот вывод для фрагмента:

STR_MAX
BAR(2049)
BAR(BAR(STR_MAX))

Я надеюсь, что эта информация будет полезной. Удачного хакинга с Clang!

person Valeriy Savchenko    schedule 09.06.2019
comment
круто, ты спас мой день. большое спасибо за такое четкое объяснение плюс код решения. - person d3v-sci; 09.06.2019