//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "AssignmentInSelectionStatementCheck.h" #include "clang/AST/IgnoreExpr.h" #include "clang/AST/StmtVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/ADT/TypeSwitch.h" using namespace clang; using namespace clang::ast_matchers; namespace { class ConditionValueCanPropagateFrom : public ConstStmtVisitor { public: llvm::SmallVector ExprToProcess; void VisitBinaryOperator(const BinaryOperator *BO) { if (BO->isCommaOp()) ExprToProcess.push_back(BO->getRHS()->IgnoreParenImpCasts()); } void VisitAbstractConditionalOperator(const AbstractConditionalOperator *CO) { ExprToProcess.push_back(CO->getFalseExpr()->IgnoreParenImpCasts()); ExprToProcess.push_back(CO->getTrueExpr()->IgnoreParenImpCasts()); } }; AST_MATCHER_P(Expr, conditionValueCanPropagateFrom, ast_matchers::internal::Matcher, InnerMatcher) { bool Found = false; ConditionValueCanPropagateFrom Visitor; Visitor.Visit(&Node); // Do not match Node itself. while (!Visitor.ExprToProcess.empty()) { const Expr *E = Visitor.ExprToProcess.pop_back_val(); ast_matchers::internal::BoundNodesTreeBuilder Result; if (InnerMatcher.matches(*E, Finder, &Result)) { Found = true; Builder->addMatch(Result); } Visitor.Visit(E); } return Found; } // Ignore implicit casts (including C++ conversion member calls) but not parens. AST_MATCHER_P(Expr, ignoringImplicitAsWritten, ast_matchers::internal::Matcher, InnerMatcher) { auto IgnoreImplicitMemberCallSingleStep = [](Expr *E) { if (auto *C = dyn_cast(E)) { Expr *ExprNode = C->getImplicitObjectArgument(); if (ExprNode->getSourceRange() == E->getSourceRange()) return ExprNode; ExprNode = ExprNode->IgnoreParenImpCasts(); if (ExprNode->getSourceRange() == E->getSourceRange()) return ExprNode; } return E; }; const Expr *IgnoreE = IgnoreExprNodes(&Node, IgnoreImplicitSingleStep, IgnoreImplicitCastsExtraSingleStep, IgnoreImplicitMemberCallSingleStep); return InnerMatcher.matches(*IgnoreE, Finder, Builder); } } // namespace namespace clang::tidy::bugprone { void AssignmentInSelectionStatementCheck::registerMatchers( MatchFinder *Finder) { auto AssignOpNoParens = ignoringImplicitAsWritten( binaryOperation(hasOperatorName("=")).bind("assignment")); auto AssignOpMaybeParens = ignoringParenImpCasts( binaryOperation(hasOperatorName("=")).bind("assignment")); auto AssignOpFromEmbeddedExpr = expr(ignoringParenImpCasts( conditionValueCanPropagateFrom(AssignOpMaybeParens))); auto CondExprWithAssign = anyOf(AssignOpNoParens, AssignOpFromEmbeddedExpr); auto OpCondExprWithAssign = anyOf(AssignOpMaybeParens, AssignOpFromEmbeddedExpr); // In these cases "single primary expression" is possible. // A single assignment within a 'ParenExpr' is allowed (but not if mixed with // other operators). auto FoundControlStmt = mapAnyOf(ifStmt, whileStmt, doStmt, forStmt) .with(hasCondition(CondExprWithAssign)); // In these cases "single primary expression" is not possible because the // assignment is already part of a bigger expression. auto FoundConditionalOperator = mapAnyOf(conditionalOperator, binaryConditionalOperator) .with(hasCondition(OpCondExprWithAssign)); auto FoundLogicalOp = binaryOperator( hasAnyOperatorName("&&", "||"), eachOf(hasLHS(OpCondExprWithAssign), hasRHS(OpCondExprWithAssign))); auto FoundSelectionStmt = stmt(anyOf(FoundControlStmt, FoundConditionalOperator, FoundLogicalOp)) .bind("parent"); Finder->addMatcher(FoundSelectionStmt, this); } void AssignmentInSelectionStatementCheck::check( const MatchFinder::MatchResult &Result) { const auto *FoundAssignment = Result.Nodes.getNodeAs("assignment"); assert(FoundAssignment); const auto *ParentStmt = Result.Nodes.getNodeAs("parent"); const StringRef CondStr = llvm::TypeSwitch(ParentStmt) .Case([](const IfStmt *) { return "condition of 'if' statement"; }) .Case( [](const Stmt *) { return "condition of a loop"; }) .Case([](const ConditionalOperator *) { return "condition of a ternary operator"; }) .Case([](const BinaryOperator *) { return "operand of a logical operator"; }) .DefaultUnreachable(); const SourceLocation OpLoc = llvm::TypeSwitch(FoundAssignment) .Case( [](const auto *Op) { return Op->getOperatorLoc(); }) .Default(FoundAssignment->getBeginLoc()); diag(OpLoc, "assignment within %0 may indicate programmer error") << FoundAssignment->getSourceRange() << CondStr; diag(OpLoc, "if it should be an assignment, move it out of the condition", DiagnosticIDs::Note); diag(OpLoc, "if it is meant to be an equality check, change '=' to '=='", DiagnosticIDs::Note); } } // namespace clang::tidy::bugprone