Index: clang-tidy/utils/ASTUtils.h =================================================================== --- clang-tidy/utils/ASTUtils.h +++ clang-tidy/utils/ASTUtils.h @@ -27,6 +27,10 @@ bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM, const LangOptions &LangOpts, StringRef FlagName); + +// Checks whether `expr` is (potentially) modified within `stmt`. +bool isModified(const Expr& expr, const Stmt& stmt, ASTContext* context); + } // namespace utils } // namespace tidy } // namespace clang Index: clang-tidy/utils/ASTUtils.cpp =================================================================== --- clang-tidy/utils/ASTUtils.cpp +++ clang-tidy/utils/ASTUtils.cpp @@ -67,6 +67,83 @@ return true; } +bool isModified(const Expr& expr, const Stmt& stmt, ASTContext* context) { + // LHS of any assignment operators. + const auto as_assignment_lhs = binaryOperator( + anyOf(hasOperatorName("="), hasOperatorName("+="), hasOperatorName("-="), + hasOperatorName("*="), hasOperatorName("/="), hasOperatorName("%="), + hasOperatorName("^="), hasOperatorName("&="), hasOperatorName("|="), + hasOperatorName(">>="), hasOperatorName("<<=")), + hasLHS(equalsNode(&expr))); + + // Operand of increment/decrement operators. + const auto as_inc_dec_operand = + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasUnaryOperand(equalsNode(&expr))); + + // Invoking non-const member function. + const auto non_const_method = cxxMethodDecl(unless(isConst())); + const auto as_non_const_this = ast_matchers::expr( + anyOf(cxxMemberCallExpr(callee(non_const_method), on(equalsNode(&expr))), + cxxOperatorCallExpr(callee(non_const_method), + hasArgument(0, equalsNode(&expr))))); + + // Used as non-const-ref argument when calling a function. + const auto non_const_ref_type = + referenceType(pointee(unless(isConstQualified()))); + const auto non_const_ref_param = forEachArgumentWithParam( + equalsNode(&expr), parmVarDecl(hasType(qualType(non_const_ref_type)))); + const auto as_non_const_ref_arg = anyOf( + callExpr(non_const_ref_param), cxxConstructExpr(non_const_ref_param)); + + // Taking address of 'exp'. + const auto as_ampersand_operand = + unaryOperator(hasOperatorName("&"), hasUnaryOperand(equalsNode(&expr))); + const auto as_pointer_from_array_decay = + castExpr(hasCastKind(CK_ArrayToPointerDecay), has(equalsNode(&expr))); + + // Check whether 'exp' is directly modified as a whole. + MatchFinder finder; + bool has_match = false; + MatchCallbackAdaptor callback( + [&has_match](const MatchFinder::MatchResult&) { has_match = true; }); + finder.addMatcher(findAll(ast_matchers::expr(anyOf( + as_assignment_lhs, as_inc_dec_operand, + as_non_const_this, as_non_const_ref_arg, + as_ampersand_operand, as_pointer_from_array_decay))), + &callback); + finder.match(stmt, *context); + if (has_match) return true; + + // Check whether any member of 'exp' is modified. + const auto member_exprs = match( + findAll(memberExpr(hasObjectExpression(equalsNode(&expr))).bind("expr")), + stmt, *context); + for (const auto& node : member_exprs) { + if (isModified(*node.getNodeAs("expr"), stmt, context)) return true; + } + + // If 'exp' is bound to a non-const reference, check all declRefExpr to that. + const auto decls = match( + ast_matchers::stmt(forEachDescendant( + varDecl(hasType(referenceType(pointee(unless(isConstQualified())))), + hasInitializer(equalsNode(&expr))) + .bind("decl"))), + stmt, *context); + for (const auto& decl_node : decls) { + const auto exprs = match( + findAll(declRefExpr(to(equalsNode(decl_node.getNodeAs("decl")))) + .bind("expr")), + stmt, *context); + for (const auto& expr_node : exprs) { + if (isModified(*expr_node.getNodeAs("expr"), stmt, context)) + return true; + } + } + + return false; +} + } // namespace utils } // namespace tidy } // namespace clang