You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何完善clang-tidy中未用assert校验除数的除法检测规则?

完善clang-tidy除数非零assert校验检查规则

需求概述

我要编写一个clang-tidy检查规则,用于检测未通过assert()校验非零的除法操作:

  • 带有assert(除数 != 0)的除法(如示例中的good_divisor)通过检查
  • 无校验的除法(如示例中的bad_divisor)需被标记
  • 要覆盖core.DivideZero无法检测的场景:比如除数为函数返回值的情况

待完成核心步骤

当前匹配器仅能检测除法运算符,还需实现以下三点:

  1. 判断除法右操作数(rhs)是变量而非表达式
  2. 检查除法语句之前是否存在assert()调用
  3. 验证存在的assert()中包含该变量不等于0的判断

示例代码

基础校验示例

#include <cassert>

int main() {
    const double good_divisor = 0.3;
    assert(good_divisor != 0.0);
    double foo = 1.0 / good_divisor; // 合法,已通过assert校验

    const double bad_divisor = 0.3;
    double bar = 1.0 / bad_divisor; // 需被标记,无校验

    return 0;
}

core.DivideZero未覆盖的场景

double foo(double x) {
    return x * 3 - 1;
}

int main() {
    double divisor = foo(0.3);
    double x = 1.0 / divisor; // 需被标记,无校验

    // 添加assert后合法,不报错
    // assert(divisor != 0);
    // double x = 1.0 / divisor;

    return 0;
}

当前实现代码

void UnsafeArithmeticCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(binaryOperator(hasOperatorName("/"))).bind("div"), this);
}

void UnsafeArithmeticCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("div");

  if (!Binop) {
    return;
  }

  diag(Binop->getExprLoc(), "found a division here", DiagnosticIDs::Note);

  auto rhs = Binop->getRHS();
  // now how do I check if this `rhs` is a variable and not an expression?
}

解决方案实现

1. 优化匹配器:定位变量类型的除数

修改registerMatchers,匹配右操作数为变量引用的除法,绑定变量节点方便后续校验:

void UnsafeArithmeticCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(
      binaryOperator(
          hasOperatorName("/"),
          hasRHS(declRefExpr(to(varDecl().bind("divisor_var"))))
      ).bind("div"),
      this);
}

2. 检查前置assert并验证有效性

在check函数中遍历除法语句之前的代码,查找符合要求的assert调用:

void UnsafeArithmeticCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("div");
  const auto *DivisorVar = Result.Nodes.getNodeAs<VarDecl>("divisor_var");
  if (!Binop || !DivisorVar) {
    return;
  }

  // 定位除法语句所在的复合语句块
  const Stmt *CurrentStmt = Binop;
  while (CurrentStmt && !isa<CompoundStmt>(CurrentStmt)) {
    CurrentStmt = CurrentStmt->getParent();
  }
  if (!CurrentStmt) {
    return;
  }

  // 遍历复合语句中除法之前的所有语句
  const CompoundStmt *Compound = cast<CompoundStmt>(CurrentStmt);
  bool HasValidAssert = false;
  for (const Stmt *S : Compound->body()) {
    if (S == Binop->getParent()) { // 到达除法语句,停止遍历
      break;
    }

    // 判断是否为assert调用
    if (const auto *Call = dyn_cast<CallExpr>(S)) {
      const FunctionDecl *Callee = Call->getDirectCallee();
      if (Callee && Callee->getName() == "assert") {
        // 检查assert参数是否为「变量 != 0」的判断
        if (const auto *NE = dyn_cast<BinaryOperator>(Call->getArg(0))) {
          if (NE->getOpcode() == BO_NE) {
            Expr *LHS = NE->getLHS()->IgnoreParenCasts();
            Expr *RHS = NE->getRHS()->IgnoreParenCasts();
            
            // 判断一侧是否为目标变量,另一侧是否为0常量
            bool IsTargetVar = (isa<DeclRefExpr>(LHS) && cast<DeclRefExpr>(LHS)->getDecl() == DivisorVar) ||
                               (isa<DeclRefExpr>(RHS) && cast<DeclRefExpr>(RHS)->getDecl() == DivisorVar);
            bool IsZero = (isa<IntegerLiteral>(LHS) && cast<IntegerLiteral>(LHS)->getValue() == 0) ||
                          (isa<FloatingLiteral>(LHS) && cast<FloatingLiteral>(LHS)->getValue().isZero()) ||
                          (isa<IntegerLiteral>(RHS) && cast<IntegerLiteral>(RHS)->getValue() == 0) ||
                          (isa<FloatingLiteral>(RHS) && cast<FloatingLiteral>(RHS)->getValue().isZero());
            
            if (IsTargetVar && IsZero) {
              HasValidAssert = true;
              break;
            }
          }
        }
      }
    }
  }

  // 未找到有效assert时触发诊断
  if (!HasValidAssert) {
    diag(Binop->getExprLoc(), "division by variable %0 without non-zero assert check")
        << DivisorVar;
    diag(DivisorVar->getLocation(), "variable declared here", DiagnosticIDs::Note);
  }
}

关键细节说明

  • 仅遍历除法语句之前的代码,确保assert在除法操作前执行
  • 调用IgnoreParenCasts()忽略括号和类型转换,避免嵌套表达式导致匹配失败
  • 同时支持整数和浮点数的0值判断,覆盖不同类型的除数
  • 精准匹配BO_NE运算符,确保断言逻辑为变量不等于0

内容的提问来源于stack exchange,提问作者Rahn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:01:20