如何计算重载运算符在具有特定类型操作数的代码库中使用的次数

How can I count number of times an overloaded operator was used in a code base with particular type of operands

本文关键字:代码 操作数 类型 计算 何计算 重载 运算符      更新时间:2023-10-16

我有一个模板化的类SafeInt<T>(由Microsoft提供)。

理论上,这个类可以用来代替POD整数类型,并且可以在算术运算期间检测任何整数溢出。

对于这个类,我编写了一些自定义的模板化重载算术运算符(+、-、*、/)函数,它们的两个参数都是SafeInt<T>的对象。

我把所有的整数类型都定义为SafeInt类类型。

我想在我的代码库中搜索上述二进制运算符的实例,其中两个操作数都是SafeInt类型。

我能想到的一些方法

  1. 使用正则表达式进行字符串搜索,并在代码中清除,以检测两个操作数都是SafeInt对象的运算符使用实例。

  2. 编写一个clang工具并处理AST来进行搜索(我还没有学会如何编写这样的工具。)

  3. 不知何故,添加一个计数器来计算自定义重载运算符的实例化次数。我花了很多时间尝试,但似乎不起作用。

有人能提出更好的方法吗?

如果我需要澄清什么,请告诉我。

谢谢。

简短回答

您可以使用clang-query命令执行此操作:

$ clang-query 
  -c='m cxxOperatorCallExpr(callee(functionDecl(hasName("operator+"))), hasArgument(0, expr(hasType(cxxRecordDecl(hasName("SafeInt"))))), hasArgument(1, expr(hasType(cxxRecordDecl(hasName("SafeInt"))))))' 
  use-si.cc --
Match #1:
/home/scott/wrk/learn/clang/clang-query1/use-si.cc:10:3: note: "root" binds here
  x + y;           // reported
  ^~~~~
1 match.

什么是clang查询

clang-query是一个实用程序,旨在帮助编写clang-tidy检查。特别是,它理解AST Matchers的语言,可以用于交互式地探索给定匹配表达式匹配的内容。然而,如图所示,它也可以非交互式地用于查找任意的AST树模式。

StephenKelly的博客文章Exploring Clang Tooling Part 2:Examine The Clang AST with Clang query为clang-query的使用提供了一个很好的介绍。

clang-query程序包含在预构建的LLVM二进制文件中,也可以按照AST Matchers教程中的说明从源代码构建。

以上命令是如何工作的

-c参数提供了一个非交互运行的命令。添加空白后,命令为:

m                                  // Match (and report) every
cxxOperatorCallExpr(               // operator function call
  callee(functionDecl(             // where the callee
    hasName("operator+"))),        // is "operator+", and
  hasArgument(0,                   // where the first argument
    expr(hasType(cxxRecordDecl(    // is a class type
      hasName("SafeInt"))))),      // called "SafeInt",
  hasArgument(1,                   // and the second argument
    expr(hasType(cxxRecordDecl(    // is also a class type
      hasName("SafeInt"))))))      // called "SafeInt".

命令行以use-si.cc --结尾,意思是分析use-si.cc,并且clang不需要额外的编译器标志来解释它

clang-query命令行具有与clang-tidy相同的基本结构,包括通过-p compile_commands.json一次扫描多个文件的能力,每个文件可能有不同的编译器选项。

示例输入

为了完整起见,我用来测试匹配器的输入是use-si.cc:

// use-si.cc
#include "SafeInt.hpp"         // SafeInt
void f1()
{
  SafeInt<int> x(2);
  SafeInt<int> y(3);
  x + y;           // reported
  x + 2;           // not reported
  2 + x;           // not reported
}

SafeInt.hpp的来源https://github.com/dcleblanc/SafeInt,在Microsoft SafeInt页面上命名的回购。

要做到这一点,您显然必须能够识别重载到特定运算符定义的运算符的单独用途。从根本上讲,您需要C++编译器前端所做的工作:解析和名称解析(包括重载)。

显然,GCC和Clang具备这一基本能力。但您希望跟踪/显示特定运算符的所有用途。您可能会弯曲Clang(或GCC,更难),以便在逐个文件的基础上为您提供这些信息。

我们的DMS软件重组工具包及其C++前端也可以用于此。DMS提供通用的解析和符号表支持机制;C++前端专门为DMS处理具有完整、准确名称解析的C++,包括GCC5和MSVS2015的过载。它的符号表实际上为范围中的每个声明收集声明的点,以及该声明在精确源位置方面的使用列表。符号作用域包括作用域中有效的每个(重载)运算符的一个条目。你可以转到所需的符号表条目,枚举/计数引用列表以获得原始计数。DMS提供了标准的API。

我们的Java源浏览器使用相同类型的符号范围/定义/使用信息来构建基于HTML的类似JavaDoc的显示,在符号声明和使用之间具有完整的HTML链接。因此,对于任何符号声明,您都可以很容易地看到其用途。

C++前端有一个类似的HTMLizer,它在C++源代码上运行。它没有那么成熟/漂亮,但很结实。目前,它还没有显示声明符号的所有用途,但这将是一个非常简单的更改。