如何完善clang-tidy中未用assert校验除数的除法检测规则?
完善clang-tidy除数非零assert校验检查规则
需求概述
我要编写一个clang-tidy检查规则,用于检测未通过assert()校验非零的除法操作:
- 带有
assert(除数 != 0)的除法(如示例中的good_divisor)通过检查 - 无校验的除法(如示例中的
bad_divisor)需被标记 - 要覆盖
core.DivideZero无法检测的场景:比如除数为函数返回值的情况
待完成核心步骤
当前匹配器仅能检测除法运算符,还需实现以下三点:
- 判断除法右操作数(rhs)是变量而非表达式
- 检查除法语句之前是否存在
assert()调用 - 验证存在的
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
相关产品推荐
相关产品推荐

