1 //===--- AssertSideEffectCheck.cpp - clang-tidy ---------------------------===//
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-exception
6 //
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 "llvm/Support/Casting.h"
19 #include <algorithm>
20 #include <string>
21
22 using namespace clang::ast_matchers;
23
24 namespace clang {
25 namespace tidy {
26 namespace bugprone {
27
28 namespace {
29
AST_MATCHER_P2(Expr,hasSideEffect,bool,CheckFunctionCalls,clang::ast_matchers::internal::Matcher<NamedDecl>,IgnoredFunctionsMatcher)30 AST_MATCHER_P2(Expr, hasSideEffect, bool, CheckFunctionCalls,
31 clang::ast_matchers::internal::Matcher<NamedDecl>,
32 IgnoredFunctionsMatcher) {
33 const Expr *E = &Node;
34
35 if (const auto *Op = dyn_cast<UnaryOperator>(E)) {
36 UnaryOperator::Opcode OC = Op->getOpcode();
37 return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc ||
38 OC == UO_PreDec;
39 }
40
41 if (const auto *Op = dyn_cast<BinaryOperator>(E)) {
42 return Op->isAssignmentOp();
43 }
44
45 if (const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
46 OverloadedOperatorKind OpKind = OpCallExpr->getOperator();
47 return OpKind == OO_Equal || OpKind == OO_PlusEqual ||
48 OpKind == OO_MinusEqual || OpKind == OO_StarEqual ||
49 OpKind == OO_SlashEqual || OpKind == OO_AmpEqual ||
50 OpKind == OO_PipeEqual || OpKind == OO_CaretEqual ||
51 OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual ||
52 OpKind == OO_PlusPlus || OpKind == OO_MinusMinus ||
53 OpKind == OO_PercentEqual || OpKind == OO_New ||
54 OpKind == OO_Delete || OpKind == OO_Array_New ||
55 OpKind == OO_Array_Delete;
56 }
57
58 if (const auto *CExpr = dyn_cast<CallExpr>(E)) {
59 bool Result = CheckFunctionCalls;
60 if (const auto *FuncDecl = CExpr->getDirectCallee()) {
61 if (FuncDecl->getDeclName().isIdentifier() &&
62 IgnoredFunctionsMatcher.matches(*FuncDecl, Finder,
63 Builder)) // exceptions come here
64 Result = false;
65 else if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))
66 Result &= !MethodDecl->isConst();
67 }
68 return Result;
69 }
70
71 return isa<CXXNewExpr>(E) || isa<CXXDeleteExpr>(E) || isa<CXXThrowExpr>(E);
72 }
73
74 } // namespace
75
AssertSideEffectCheck(StringRef Name,ClangTidyContext * Context)76 AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name,
77 ClangTidyContext *Context)
78 : ClangTidyCheck(Name, Context),
79 CheckFunctionCalls(Options.get("CheckFunctionCalls", false)),
80 RawAssertList(Options.get("AssertMacros", "assert,NSAssert,NSCAssert")),
81 IgnoredFunctions(utils::options::parseListPair(
82 "__builtin_expect;", Options.get("IgnoredFunctions", ""))) {
83 StringRef(RawAssertList).split(AssertMacros, ",", -1, false);
84 }
85
86 // The options are explained in AssertSideEffectCheck.h.
storeOptions(ClangTidyOptions::OptionMap & Opts)87 void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
88 Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls);
89 Options.store(Opts, "AssertMacros", RawAssertList);
90 Options.store(Opts, "IgnoredFunctions",
91 utils::options::serializeStringList(IgnoredFunctions));
92 }
93
registerMatchers(MatchFinder * Finder)94 void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) {
95 auto IgnoredFunctionsMatcher =
96 matchers::matchesAnyListedName(IgnoredFunctions);
97
98 auto DescendantWithSideEffect =
99 traverse(TK_AsIs, hasDescendant(expr(hasSideEffect(
100 CheckFunctionCalls, IgnoredFunctionsMatcher))));
101 auto ConditionWithSideEffect = hasCondition(DescendantWithSideEffect);
102 Finder->addMatcher(
103 stmt(
104 anyOf(conditionalOperator(ConditionWithSideEffect),
105 ifStmt(ConditionWithSideEffect),
106 unaryOperator(hasOperatorName("!"),
107 hasUnaryOperand(unaryOperator(
108 hasOperatorName("!"),
109 hasUnaryOperand(DescendantWithSideEffect))))))
110 .bind("condStmt"),
111 this);
112 }
113
check(const MatchFinder::MatchResult & Result)114 void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) {
115 const SourceManager &SM = *Result.SourceManager;
116 const LangOptions LangOpts = getLangOpts();
117 SourceLocation Loc = Result.Nodes.getNodeAs<Stmt>("condStmt")->getBeginLoc();
118
119 StringRef AssertMacroName;
120 while (Loc.isValid() && Loc.isMacroID()) {
121 StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LangOpts);
122
123 // Check if this macro is an assert.
124 if (llvm::is_contained(AssertMacros, MacroName)) {
125 AssertMacroName = MacroName;
126 break;
127 }
128 Loc = SM.getImmediateMacroCallerLoc(Loc);
129 }
130 if (AssertMacroName.empty())
131 return;
132
133 diag(Loc, "side effect in %0() condition discarded in release builds")
134 << AssertMacroName;
135 }
136
137 } // namespace bugprone
138 } // namespace tidy
139 } // namespace clang
140