【问题标题】:How to exclude headers from AST in clang?如何在 clang 中从 AST 中排除标头?
【发布时间】:2015-03-18 20:48:06
【问题描述】:

我正在使用 clang 生成 AST。我有以下文件(lambda.cpp)要解析:

#include <iostream>

void my_lambda()
{
    auto lambda = [](auto x, auto y) {return x + y;};
    std::cout << "fabricati diem"; 
}

我正在使用以下命令解析它:

clang -Xclang -ast-dump -fsyntax-only lambda.cpp

问题在于,clang 也解析了标题内容。结果,我得到了相当大的(约 3000 行)文件,其中包含无用的(对我而言)内容。

生成 AST 时如何排除标头?

【问题讨论】:

  • 当 clang 需要从标头中获取名称/定义/等以便为源文件生成 AST 时,您希望它做什么?
  • @MarkB 我一定表达得很糟糕。我想在解析过程中使用头文件,但在我的文件中只显示 AST - 没有来自头文件的 AST。
  • Header_gurads 不会做这项工作吗?
  • 你真正想做什么?我已经使用了 clang python 后端,通过查看 ast 我知道你正在经历的痛苦。有几个选项可以组合你想要的东西。 iirc 您可以从 clang 获取行号信息并忽略之前的所有内容。

标签: c++ clang abstract-syntax-tree


【解决方案1】:

clang-check 可能对此事有用,clang-check 有选项 -ast-dump-filter=&lt;string&gt; 记录如下

-ast-dump-filter= - 与 -ast-dump 或 -ast-print 一起使用以仅转储/打印在 a 中具有特定子字符串的 AST 声明节点 限定名称。使用 -ast-list 列出所有可过滤的声明节点 名字。

当 clang-check 在示例代码 (lambda.cpp) 上使用 -ast-dump-filter=my_lambda 运行时

#include <iostream>

void my_lambda()
{
    auto lambda = [](auto x, auto y) {return x + y;};
    std::cout << "fabricati diem"; 
}

它只转储匹配的声明节点FunctionDecl my_lambda 'void (void)'

这是命令行参数和输出中的几行。

$ clang-check -extra-arg=-std=c++1y -ast-dump -ast-dump-filter=my_lambda lambda.cpp --

FunctionDecl 0x2ddf630 <lambda.cpp:3:1, line:7:1> line:3:6 my_lambda 'void (void)'
`-CompoundStmt 0x2de1558 <line:4:1, line:7:1>
  |-DeclStmt 0x2de0960 <line:5:9, col:57>

【讨论】:

  • clang-check 看起来很整洁!
  • @alper 你知道是否有一种方法可以获取所有节点,而无需导入你想从中获取 AST 的类的所有依赖项?我希望能够在不必解析依赖项(传递框架路径)的情况下获取类的语法节点,以便我可以以特定方式重写同一个类,我是否在正确的路径上查看 AST 来实现这一点?任何评论表示赞赏!
【解决方案2】:

过滤特定标识符很好,使用-ast-dump-filter。但是,如果您想从一个文件中的所有标识符获取 ast 怎么办?

我想出了以下解决方案:

在包含后添加一个可识别的行:

#include <iostream>
int XX_MARKER_XX = 123234; // marker line for ast-dump
void my_lambda()
...

然后用

转储 ast
clang-check -extra-arg=-std=c++1y -ast-dump lambda.cpp > ast.txt

您可以轻松地将XX_MARKER_XX 之前的所有内容与sed 删除:

cat ast.txt | sed -n '/XX_MARKER_XX/,$p'  | less

还有很多,但对于更大的文件更有用。

【讨论】:

  • 出于我的需要,我不初始化 XX_MARKER_XX 并将命令行缩短为 clang++ -Xclang -ast-dump minimal.cpp | sed -n '/XX_MARKER_XX/,$p'。
【解决方案3】:

这是 C++ 的问题,而不是 clang:C++ 中没有文件,只有编译单元。当您 #include 一个文件时,您将所述文件中的所有定义(递归)包含到您的编译单元中,并且无法区分它们(这是标准期望您的编译器执行的操作)。

想象一个不同的场景:

/////////////////////////////
// headertmp.h
#if defined(A)
    struct Foo {
        int bar;
    };
#elif defined(B)
    struct Foo {
        short bar;
    };
#endif

/////////////////////////////
// foobar.cpp
#ifndef A
# define B
#endif

#include "headertmp.h"

void foobar(Foo foo) {
    // do stuff to foo.bar
}

您的 foobar.cpp 声明了一个名为 Foo 的结构和一个名为 foobar 的函数,但 headertmp.h 本身没有定义任何 Foo,除非定义了 A 或 B。只有在 foobar 的编译单元中两者合二为一,你才能理解headertmp.h。

如果您对编译单元内的声明子集感兴趣,则必须直接从生成的 AST 中提取必要的信息(类似于链接器在将不同编译单元链接在一起时必须执行的操作)。当然,您可以在解析器提取的任何元数据上过滤此编译单元的 AST。

【讨论】:

  • 虽然在翻译单元的完整编译过程中确实应该在头文件中的代码和非头文件中的代码之间没有感知到差异,但这个问题是关于限制 AST 提取,以及没有理由不可能做到这一点(从其他答案中可以看出)。
  • @KyleStrand 请阅读我的最后一段,它准确表达了您的评论要点:您可以将提取/转储限制为编译单元的一部分,而不是排除特定的头文件 i>(因为 AST 中没有这样的概念)
  • 您的回答不仅仅是说不能跳过头文件;它表示其中的定义无法与直接在文件中的定义区分开来。这适用于编译,但不一定适用于原始 AST 提取。
  • @KyleStrand 我不同意或不理解您的论点:您能在我的示例中告诉我,如何仅从标题中提取 AST 吗? IE。你什么时候做预处理?
  • 您的意思是仅从非标题中提取它吗?很简单:预处理器需要记录哪些行来自哪些文件(现代预处理器通常会这样做),并且 AST-dump 工具只会从非头文件中转储定义。
【解决方案4】:

转储的 AST 对每个节点都有一些源文件的指示。所以转储的 AST 可以根据二级 AST 节点的loc 数据进行过滤。

您需要将loc 中的file 和expansionLoc 中expansionLoc 中的file 与顶级文件的名称相匹配。这似乎对我很有效。由于某种原因,某些节点不包含这些元素。带有isImplicit 的节点应该可以安全跳过,但我不确定没有文件名信息的其他节点会发生什么。

以下 python 脚本使用这些规则将“astdump.json”过滤为“astdump.filtered.json”(以流方式进行转换留给读者练习):

#! /usr/bin/python3

import json
import sys

if len(sys.argv) != 2:
    print('Usage: ' + sys.argv[0] + ' filename')
    sys.exit(1)

filename = sys.argv[1]

with open('astdump.json', 'rb') as input, open('astdump.filtered.json', 'w') as output:
    toplevel = json.load(input)
    new_inner = []
    for o in toplevel['inner']:
        if o.get('isImplicit', False):
            continue

        file_name = None
        loc = o.get('loc', {})
        if 'file' in loc:
            file_name = loc['file']

        if 'expansionLoc' in loc:
            if 'file' in loc['expansionLoc']:
                file_name = loc['expansionLoc']['file']

        if file_name != filename:
            continue

        new_inner.append(o)

    toplevel['inner'] = new_inner
    json.dump(toplevel, output, indent=4)

【讨论】:

    猜你喜欢
    • 2019-10-01
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-04
    • 1970-01-01
    • 1970-01-01
    • 2016-01-04
    相关资源
    最近更新 更多