【问题标题】:Find all references of specific function declaration in libclang (Python)在 libclang (Python) 中查找特定函数声明的所有引用
【发布时间】:2016-09-08 17:57:50
【问题描述】:

在 Python 中通过 libclang 解析 C++ 源文件时,我试图查找(行和列位置)特定函数声明的所有引用

例如:

#include <iostream>
using namespace std;

int addition (int a, int b)
{
  int r;
  r=a+b;
  return r;
}

int main ()
{
  int z, q;
  z = addition (5,3);
  q = addition (5,5);
  cout << "The first result is " << z;
  cout << "The second result is " << q;
}

因此,对于上面的源文件,我希望在第 5 行中为 addition 的函数声明,我希望 find_all_function_decl_references(见下文)在第 15 行和第 16 行返回 addition 的引用.

我试过这个(改编自here

import clang.cindex
import ccsyspath

index = clang.cindex.Index.create()
translation_unit = index.parse(filename, args=args)

for node in translation_unit.cursor.walk_preorder():
    node_definition = node.get_definition()

    if node.location.file is None:
        continue
    if node.location.file.name != sourcefile:
        continue
    if node_def is None:
        pass
    if node.kind.name == 'FUNCTION_DECL':
        if node.kind.is_reference():
          find_all_function_decl_references(node_definition.displayname)  # TODO

另一种方法是存储在列表中找到的所有函数声明,并在每个函数上运行find_all_function_decl_references 方法。

有没有人知道如何解决这个问题?这个find_all_function_decl_references 方法会怎么样? (我是 libclang 和 Python 的新手。)

我已经看到this def find_typerefs 在其中找到对某种类型的所有引用,但我不确定如何根据我的需要实现它。

理想情况下,我希望能够获取任何声明的所有引用;不仅是函数,还有变量声明、参数声明(例如上面第 7 行示例中的 ab)、类声明等。

编辑Andrew's 评论之后,这里有一些关于我的设置规范的详细信息:

  • LLVM 3.8.0-win64
  • libclang-py3 3.8.1
  • Python3.5.1(在 Windows 中,我假设是 CPython)
  • 对于args,我尝试了答案here 中建议的方法和another 答案中建议的方法。

*请注意,鉴于我的编程经验很少,我希望能得到一个简单解释其工作原理的答案。

【问题讨论】:

  • 不适用于 clang/python,但请参阅 stackoverflow.com/a/37149988/120163 了解有关如何“查找对特定符号/运算符的所有引用”的另一种变体
  • @IraBaxter 感谢您的链接,但我只对通过libclang 实现这一目标感兴趣。

标签: python c++ clang libclang


【解决方案1】:

真正使这个问题具有挑战性的是 C++ 的复杂性。

考虑 C++ 中可调用的内容:函数、lambda、函数调用运算符、成员函数、模板函数和成员模板函数。因此,在仅匹配调用表达式的情况下,您需要能够消除这些情况的歧义。

此外,libclang 不提供 clang AST 的完美视图(一些节点没有完全暴露,尤其是一些与模板相关的节点)。因此,任意代码片段有可能(甚至很可能)包含一些构造,其中 AST 的 libclangs 视图不足以将调用表达式与声明相关联。

但是,如果您准备将自己限制在语言的一个子集上,则可能会取得一些进展 - 例如,以下示例尝试将调用站点与函数声明相关联。它通过使用调用表达式对 AST 匹配函数声明中的所有节点进行单次传递来实现这一点。

from clang.cindex import *

def is_function_call(funcdecl, c):
    """ Determine where a call-expression cursor refers to a particular function declaration
    """
    defn = c.get_definition()
    return (defn is not None) and (defn == funcdecl)

def fully_qualified(c):
    """ Retrieve a fully qualified function name (with namespaces)
    """
    res = c.spelling
    c = c.semantic_parent
    while c.kind != CursorKind.TRANSLATION_UNIT:
        res = c.spelling + '::' + res
        c = c.semantic_parent
    return res

def find_funcs_and_calls(tu):
    """ Retrieve lists of function declarations and call expressions in a translation unit
    """
    filename = tu.cursor.spelling
    calls = []
    funcs = []
    for c in tu.cursor.walk_preorder():
        if c.location.file is None:
            pass
        elif c.location.file.name != filename:
            pass
        elif c.kind == CursorKind.CALL_EXPR:
            calls.append(c)
        elif c.kind == CursorKind.FUNCTION_DECL:
            funcs.append(c)
    return funcs, calls

idx = Index.create()
args =  '-x c++ --std=c++11'.split()
tu = idx.parse('tmp.cpp', args=args)
funcs, calls = find_funcs_and_calls(tu)
for f in funcs:
    print(fully_qualified(f), f.location)
    for c in calls:
        if is_function_call(f, c):
            print('-', c)
    print()

为了展示它的效果如何,您需要一个更具挑战性的示例来解析:

// tmp.cpp
#include <iostream>
using namespace std;

namespace impl {
    int addition(int x, int y) {
        return x + y;
    }

    void f() {
        addition(2, 3);
    }
}

int addition (int a, int b) {
  int r;
  r=a+b;
  return r;
}

int main () {
  int z, q;
  z = addition (5,3);
  q = addition (5,5);
  cout << "The first result is " << z;
  cout << "The second result is " << q;
}

我得到了输出:

impl::addition
- <SourceLocation file 'tmp.cpp', line 10, column 9>

impl::f

addition
- <SourceLocation file 'tmp.cpp', line 22, column 7>
- <SourceLocation file 'tmp.cpp', line 23, column 7>

main

扩大规模以考虑更多类型的声明(IMO)将是不平凡的,并且本身就是一个有趣的项目。

寻址 cmets

鉴于有一些关于此答案中的代码是否产生我提供的结果的问题,我添加了一个gist of the code(重现了这个问题的内容)和一个非常小的vagrant@987654323 @ 你可以用来试验。机器启动后,您可以克隆 gist,并使用以下命令重现答案:

git clone https://gist.github.com/AndrewWalker/daa2af23f34fe9a6acc2de579ec45535 find-func-decl-refs
cd find-func-decl-refs
export LD_LIBRARY_PATH=/usr/lib/llvm-3.8/lib/ && python3 main.py

【讨论】:

  • 非常感谢您的详细回答。奇怪的是,与你的输出相比,我得到了不同的输出:impl::addition &lt;SourceLocation file 'tmp.cpp', line 5, column 9&gt; - &lt;clang.cindex.Cursor object at 0x009AAC60&gt; impl::f &lt;SourceLocation file 'tmp.cpp', line 9, column 10&gt; addition &lt;SourceLocation file 'tmp.cpp', line 14, column 5&gt; - &lt;clang.cindex.Cursor object at 0x009B5E90&gt; - &lt;clang.cindex.Cursor object at 0x009C3260&gt; main &lt;SourceLocation file 'tmp.cpp', line 20, column 5&gt;。请注意第 22 和 23 行没有任何结果。有什么想法吗??
  • 您能否在问题中添加有关您正在使用的 clang 和 libclang 版本、传递给解析的参数和选项集以及目标平台的描述。如果你在 Windows 上,你还可以添加哪个 Python 发行版(cpython、anaconda)和版本信息类型'import sys; print(sys.version)' 在解释器中
  • LLVM 3.8.0-win64、libclang-py3 3.8.1、Windows 中的 Python3.5.1。不知道如何找出 Python 发行版;我假设它的 CPython。对于 args,我尝试了您在此处的答案中建议的那些以及来自您的另一个 answers 的那些。
  • @AndrewWalker 我也没有得到您在答案中发布的输出。能否请您解释一下方法(主要是fully_qualifiedis_function_call),以便我更好地理解它?
  • @Karim - 我很难重现您描述的环境 - Windows 上的 64 位 clang 和 64 位 python 似乎不能很好地结合在一起 - 过去,我已经如果我使用所有东西的 32 位版本,就会取得一些成功。因此,我没有让您猜测它们之间的区别,而是努力提供一个可在 Windows 上使用的可重现环境(通过 vagrant / virtualbox)
猜你喜欢
  • 1970-01-01
  • 2015-03-23
  • 2014-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多