150 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "AssertSideEffectCheck.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/AST/ASTContext.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Lex/Lexer.h"16#include "llvm/ADT/SmallVector.h"17#include "llvm/ADT/StringRef.h"18#include <string>19 20using namespace clang::ast_matchers;21 22namespace clang::tidy::bugprone {23 24namespace {25 26AST_MATCHER_P2(Expr, hasSideEffect, bool, CheckFunctionCalls,27 clang::ast_matchers::internal::Matcher<NamedDecl>,28 IgnoredFunctionsMatcher) {29 const Expr *E = &Node;30 31 if (const auto *Op = dyn_cast<UnaryOperator>(E)) {32 const UnaryOperator::Opcode OC = Op->getOpcode();33 return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc ||34 OC == UO_PreDec;35 }36 37 if (const auto *Op = dyn_cast<BinaryOperator>(E)) {38 return Op->isAssignmentOp();39 }40 41 if (const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E)) {42 if (const auto *MethodDecl =43 dyn_cast_or_null<CXXMethodDecl>(OpCallExpr->getDirectCallee()))44 if (MethodDecl->isConst())45 return false;46 47 const OverloadedOperatorKind OpKind = OpCallExpr->getOperator();48 return OpKind == OO_Equal || OpKind == OO_PlusEqual ||49 OpKind == OO_MinusEqual || OpKind == OO_StarEqual ||50 OpKind == OO_SlashEqual || OpKind == OO_AmpEqual ||51 OpKind == OO_PipeEqual || OpKind == OO_CaretEqual ||52 OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual ||53 OpKind == OO_LessLess || OpKind == OO_GreaterGreater ||54 OpKind == OO_PlusPlus || OpKind == OO_MinusMinus ||55 OpKind == OO_PercentEqual || OpKind == OO_New ||56 OpKind == OO_Delete || OpKind == OO_Array_New ||57 OpKind == OO_Array_Delete;58 }59 60 if (const auto *CExpr = dyn_cast<CallExpr>(E)) {61 if (!CheckFunctionCalls)62 return false;63 if (const auto *FuncDecl = CExpr->getDirectCallee()) {64 if (FuncDecl->getDeclName().isIdentifier() &&65 IgnoredFunctionsMatcher.matches(*FuncDecl, Finder,66 Builder)) // exceptions come here67 return false;68 for (size_t I = 0; I < FuncDecl->getNumParams(); I++) {69 const ParmVarDecl *P = FuncDecl->getParamDecl(I);70 const Expr *ArgExpr =71 I < CExpr->getNumArgs() ? CExpr->getArg(I) : nullptr;72 const QualType PT = P->getType().getCanonicalType();73 if (ArgExpr && !ArgExpr->isXValue() && PT->isReferenceType() &&74 !PT.getNonReferenceType().isConstQualified())75 return true;76 }77 if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))78 return !MethodDecl->isConst();79 }80 return true;81 }82 83 return isa<CXXNewExpr>(E) || isa<CXXDeleteExpr>(E) || isa<CXXThrowExpr>(E);84}85 86} // namespace87 88AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name,89 ClangTidyContext *Context)90 : ClangTidyCheck(Name, Context),91 CheckFunctionCalls(Options.get("CheckFunctionCalls", false)),92 RawAssertList(Options.get("AssertMacros", "assert,NSAssert,NSCAssert")),93 IgnoredFunctions(utils::options::parseListPair(94 "__builtin_expect;", Options.get("IgnoredFunctions", ""))) {95 RawAssertList.split(AssertMacros, ",", -1, false);96}97 98// The options are explained in AssertSideEffectCheck.h.99void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {100 Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls);101 Options.store(Opts, "AssertMacros", RawAssertList);102 Options.store(Opts, "IgnoredFunctions",103 utils::options::serializeStringList(IgnoredFunctions));104}105 106void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) {107 auto IgnoredFunctionsMatcher =108 matchers::matchesAnyListedName(IgnoredFunctions);109 110 auto DescendantWithSideEffect =111 traverse(TK_AsIs, hasDescendant(expr(hasSideEffect(112 CheckFunctionCalls, IgnoredFunctionsMatcher))));113 auto ConditionWithSideEffect = hasCondition(DescendantWithSideEffect);114 Finder->addMatcher(115 stmt(116 anyOf(conditionalOperator(ConditionWithSideEffect),117 ifStmt(ConditionWithSideEffect),118 unaryOperator(hasOperatorName("!"),119 hasUnaryOperand(unaryOperator(120 hasOperatorName("!"),121 hasUnaryOperand(DescendantWithSideEffect))))))122 .bind("condStmt"),123 this);124}125 126void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) {127 const SourceManager &SM = *Result.SourceManager;128 const LangOptions LangOpts = getLangOpts();129 SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getBeginLoc();130 131 StringRef AssertMacroName;132 while (Loc.isValid() && Loc.isMacroID()) {133 const StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LangOpts);134 Loc = SM.getImmediateMacroCallerLoc(Loc);135 136 // Check if this macro is an assert.137 if (llvm::is_contained(AssertMacros, MacroName)) {138 AssertMacroName = MacroName;139 break;140 }141 }142 if (AssertMacroName.empty())143 return;144 145 diag(Loc, "side effect in %0() condition discarded in release builds")146 << AssertMacroName;147}148 149} // namespace clang::tidy::bugprone150