【问题标题】:clang fails replacing a statement if it contains a macro如果语句包含宏,则 clang 无法替换语句
【发布时间】:2014-07-26 14:40:52
【问题描述】:

我正在使用 clang 尝试解析(使用 C++ API)一些 C++ 文件,并使所有大小写 - 断点对使用特定样式。

示例

**Original**

switch(...)
{
   case 1:
   {
      <code>
   }break;

   case 2:
   {
      <code>
      break;
   }
}

**After replacement**

switch(...)
{
   case 1:
   {
      <code>
      break;
   }

   case 2:
   {
      <code>
      break;
   }
}

如果代码部分不包含任何宏,我目前所拥有的正是我想要的。 我的问题是:clang 是否以不同的方式对待扩展(如果我转储有问题的语句,它将显示扩展版本)宏?如果是这样,我怎样才能让它工作?

可能有帮助的其他信息:

我使用Rewriter::ReplaceStmt 用新创建的 CompoundStmt 替换每个案例的子语句,我注意到如果“from”参数返回 true,ReplaceStmt包含一个宏,并且该方法返回 true 的唯一方式是 if

Rewriter::getRangeSize(from->getSourceRange())

返回 -1

【问题讨论】:

    标签: c++ clang clang++


    【解决方案1】:

    你的问题是SourceLocation的设计造成的。

    下面是一篇文章:


    使用 clang 的 SourceLocation 进行宏扩展

    SourceLocation 的设计足够灵活,可以同时处理未展开的位置和宏展开的位置

    如果令牌是扩展的结果,则需要考虑两个不同的位置:拼写位置(与令牌对应的字符的位置)和实例化位置(使用令牌的位置 - 宏实例化点)。

    我们以下面这个简单的源文件为例:

    #define MACROTEST bool
    
    int main() {
    
        int var = 2;
        switch(var)
        {
           case 1:
           {
              MACROTEST newvar;
           }break;
    
           case 2:
           {
              MACROTEST newvar;
              break;
           }
        }
    
        return 0;
    }
    

    假设我们要替换两个声明语句

    MACROTEST newvar;
    

    带有声明语句

    int var = 2;
    

    为了得到这样的东西

    #define MACROTEST bool
    
    int main() {
    
        int var = 2;
        switch(var)
        {
           case 1:
           {
              int var = 2;
           }break;
    
           case 2:
           {
              int var = 2;
              break;
           }
        }
    
        return 0;
    }
    

    如果我们输出 AST (-ast-dump),我们会得到以下信息(我包含了一张图片,因为它比无色文本更直观):

    您可以看到我们感兴趣的第一个 DeclStmt 报告的位置,从第 1 行到第 10 行:这意味着 clang 在转储中报告从宏行到宏所在点的间隔使用:

    #define MACROTEST [from_here]bool
    
    int main() {
    
        int var = 2;
        switch(var)
        {
           case 1:
           {
              MACROTEST newvar[to_here];
           }break;
    
           case 2:
           {
              MACROTEST newvar;
              break;
           }
        }
    
        return 0;
    }
    

    (请注意,由于我的文本编辑器使用制表符,因此字符数可能与普通空格不同)

    最终,这将触发 Rewriter::getRangeSize 失败 (-1) 和随后的 Rewriter::ReplaceStmt true 返回值(这意味着失败 - 请参阅文档)。

    发生的情况如下:您收到几个 SourceLocation 标记,其中第一个是宏 ID(isMacroID() 将返回 true),而后者不是。

    为了成功获得宏扩展语句的范围,我们需要退后一步并与SourceManager 进行通信,这是您所有拼写位置和实例化位置(如果您不记得这些术语,请退后一步)需要。我不能比in the documentation提供的详细描述更清楚:

    可以查询 SourceManager 以获取有关 SourceLocation 的信息 对象,将它们变成拼写或扩展位置。 拼写位置表示与令牌对应的字节的位置 来自,扩展位置表示该位置所在的位置 用户的看法。例如,在宏扩展的情况下, 拼写位置指示扩展标记的来源和 展开位置指定展开的位置。

    此时您应该明白我为什么首先解释所有这些内容:如果您打算使用源范围进行替换,则需要使用适当的扩展间隔。。 p>

    回到我提出的示例,这是实现它的代码:

    SourceLocation startLoc = declaration_statement->getLocStart();
    SourceLocation endLoc = declaration_statement->getLocEnd();
    
    if( startLoc.isMacroID() ) {
        // Get the start/end expansion locations
        std::pair< SourceLocation, SourceLocation > expansionRange = 
                 rewriter.getSourceMgr().getImmediateExpansionRange( startLoc );
    
        // We're just interested in the start location
        startLoc = expansionRange.first;
    }
    
    if( endLoc.isMacroID() ) {
      // will not be executed
    }
    
    SourceRange expandedLoc( startLoc, endLoc );
    bool failure = rewriter.ReplaceText( expandedLoc, 
                                         replacer_statement->getSourceRange() );
    
    if( !failure )
        std::cout << "This will get printed if you did it correctly!";
    

    declaration_statement 是两者之一

    MACROTEST newvar;
    

    replacer_statement 是用于替换的语句

    int var = 2;
    

    上面的代码会告诉你:

    #define MACROTEST bool
    
    int main() {
    
        int var = 2;
        switch(var)
        {
           case 1:
           {
              int var = 2;
           }break;
    
           case 2:
           {
              int var = 2;
              break;
           }
        }
    
        return 0;
    }
    

    即宏扩展语句的完整且成功的替换。


    参考资料:

    【讨论】:

      【解决方案2】:

      为了获取与宏扩展相关的文件位置,可以使用API​​函数来检索信息:

      SourceLocation startLoc = rewriter.getSourceMgr().getFileLoc(
          declaration_statement->getLocStart());
      SourceLocation endLoc = rewriter.getSourceMgr().getFileLoc(
          declaration_statement->getLocEnd());
      

      这个 API 函数的作用与 Marco 在他的代码中写的一样,但是是自动的。

      如果我们看函数getFileLoc()的实现:

      这是函数的描述: 给定 Loc,如果它是宏位置,则返回扩展位置或拼写位置,这取决于它是否来自宏参数。

       SourceLocation getFileLoc(SourceLocation Loc) const {
           if (Loc.isFileID()) return Loc;
               return getFileLocSlowCase(Loc);
       }
      
      SourceLocation SourceManager::getFileLocSlowCase(SourceLocation Loc) const {
          do {
              if (isMacroArgExpansion(Loc))
                  Loc = getImmediateSpellingLoc(Loc);
              else
                  Loc = getImmediateExpansionRange(Loc).first;
          } while (!Loc.isFileID());
          return Loc;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-18
        • 1970-01-01
        • 2017-07-12
        • 1970-01-01
        • 2014-06-14
        • 2011-02-13
        • 2021-10-30
        • 1970-01-01
        相关资源
        最近更新 更多