xref: /llvm-project/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp (revision b77e40265caf1fc459b1c57ac495bbd48e1f7942)
1ace64403SRoman Lebedev //===--- FunctionCognitiveComplexityCheck.cpp - clang-tidy ------*- C++ -*-===//
2ace64403SRoman Lebedev //
3ace64403SRoman Lebedev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ace64403SRoman Lebedev // See https://llvm.org/LICENSE.txt for license information.
5ace64403SRoman Lebedev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ace64403SRoman Lebedev //
7ace64403SRoman Lebedev //===----------------------------------------------------------------------===//
8ace64403SRoman Lebedev 
9ace64403SRoman Lebedev #include "FunctionCognitiveComplexityCheck.h"
10ace64403SRoman Lebedev #include "../ClangTidyDiagnosticConsumer.h"
11ace64403SRoman Lebedev #include "clang/AST/Decl.h"
12ace64403SRoman Lebedev #include "clang/AST/DeclBase.h"
13ace64403SRoman Lebedev #include "clang/AST/Expr.h"
14ace64403SRoman Lebedev #include "clang/AST/RecursiveASTVisitor.h"
15ace64403SRoman Lebedev #include "clang/AST/Stmt.h"
16ace64403SRoman Lebedev #include "clang/ASTMatchers/ASTMatchFinder.h"
17ace64403SRoman Lebedev #include "clang/ASTMatchers/ASTMatchers.h"
18ace64403SRoman Lebedev #include "clang/ASTMatchers/ASTMatchersInternal.h"
19ace64403SRoman Lebedev #include "clang/Basic/Diagnostic.h"
20ace64403SRoman Lebedev #include "clang/Basic/DiagnosticIDs.h"
21ace64403SRoman Lebedev #include "clang/Basic/LLVM.h"
22ace64403SRoman Lebedev #include "clang/Basic/SourceLocation.h"
23c60ac509SKazu Hirata #include "llvm/ADT/STLForwardCompat.h"
24ace64403SRoman Lebedev #include "llvm/ADT/SmallVector.h"
25ace64403SRoman Lebedev #include "llvm/Support/Casting.h"
26ace64403SRoman Lebedev #include "llvm/Support/ErrorHandling.h"
27ace64403SRoman Lebedev #include <array>
28ace64403SRoman Lebedev #include <cassert>
2971f55735SKazu Hirata #include <optional>
30ace64403SRoman Lebedev #include <stack>
31ace64403SRoman Lebedev #include <tuple>
32ace64403SRoman Lebedev #include <type_traits>
33ace64403SRoman Lebedev #include <utility>
34ace64403SRoman Lebedev 
35ace64403SRoman Lebedev using namespace clang::ast_matchers;
36ace64403SRoman Lebedev 
377d2ea6c4SCarlos Galvez namespace clang::tidy::readability {
38ace64403SRoman Lebedev namespace {
39ace64403SRoman Lebedev 
40ace64403SRoman Lebedev struct CognitiveComplexity final {
41ace64403SRoman Lebedev   // Any increment is based on some combination of reasons.
42ace64403SRoman Lebedev   // For details you can look at the Specification at
43ace64403SRoman Lebedev   // https://www.sonarsource.com/docs/CognitiveComplexity.pdf
44ace64403SRoman Lebedev   // or user-facing docs at
45165d6933SNathan James   // http://clang.llvm.org/extra/clang-tidy/checks/readability/function-cognitive-complexity.html
46ace64403SRoman Lebedev   // Here are all the possible reasons:
47ace64403SRoman Lebedev   enum Criteria : uint8_t {
48ace64403SRoman Lebedev     None = 0U,
49ace64403SRoman Lebedev 
50ace64403SRoman Lebedev     // B1, increases cognitive complexity (by 1)
51ace64403SRoman Lebedev     // What causes it:
52ace64403SRoman Lebedev     // * if, else if, else, ConditionalOperator (not BinaryConditionalOperator)
53ace64403SRoman Lebedev     // * SwitchStmt
54ace64403SRoman Lebedev     // * ForStmt, CXXForRangeStmt
55ace64403SRoman Lebedev     // * WhileStmt, DoStmt
56ace64403SRoman Lebedev     // * CXXCatchStmt
57ace64403SRoman Lebedev     // * GotoStmt, IndirectGotoStmt (but not BreakStmt, ContinueStmt)
58ace64403SRoman Lebedev     // * sequences of binary logical operators (BinOpLAnd, BinOpLOr)
59ace64403SRoman Lebedev     // * each method in a recursion cycle (not implemented)
60ace64403SRoman Lebedev     Increment = 1U << 0,
61ace64403SRoman Lebedev 
62ace64403SRoman Lebedev     // B2, increases current nesting level (by 1)
63ace64403SRoman Lebedev     // What causes it:
64ace64403SRoman Lebedev     // * if, else if, else, ConditionalOperator (not BinaryConditionalOperator)
65ace64403SRoman Lebedev     // * SwitchStmt
66ace64403SRoman Lebedev     // * ForStmt, CXXForRangeStmt
67ace64403SRoman Lebedev     // * WhileStmt, DoStmt
68ace64403SRoman Lebedev     // * CXXCatchStmt
69ace64403SRoman Lebedev     // * nested CXXConstructor, CXXDestructor, CXXMethod (incl. C++11 Lambda)
70ace64403SRoman Lebedev     // * GNU Statement Expression
71ace64403SRoman Lebedev     // * Apple Block declaration
72ace64403SRoman Lebedev     IncrementNesting = 1U << 1,
73ace64403SRoman Lebedev 
74ace64403SRoman Lebedev     // B3, increases cognitive complexity by the current nesting level
75ace64403SRoman Lebedev     // Applied before IncrementNesting
76ace64403SRoman Lebedev     // What causes it:
77ace64403SRoman Lebedev     // * IfStmt, ConditionalOperator (not BinaryConditionalOperator)
78ace64403SRoman Lebedev     // * SwitchStmt
79ace64403SRoman Lebedev     // * ForStmt, CXXForRangeStmt
80ace64403SRoman Lebedev     // * WhileStmt, DoStmt
81ace64403SRoman Lebedev     // * CXXCatchStmt
82ace64403SRoman Lebedev     PenalizeNesting = 1U << 2,
83ace64403SRoman Lebedev 
84ace64403SRoman Lebedev     All = Increment | PenalizeNesting | IncrementNesting,
85ace64403SRoman Lebedev   };
86ace64403SRoman Lebedev 
87ace64403SRoman Lebedev   // The helper struct used to record one increment occurrence, with all the
88ade0662cSSalman Javed   // details necessary.
89ace64403SRoman Lebedev   struct Detail {
90ace64403SRoman Lebedev     const SourceLocation Loc;     // What caused the increment?
91ace64403SRoman Lebedev     const unsigned short Nesting; // How deeply nested is Loc located?
92ace64403SRoman Lebedev     const Criteria C;             // The criteria of the increment
93ace64403SRoman Lebedev 
94ace64403SRoman Lebedev     Detail(SourceLocation SLoc, unsigned short CurrentNesting, Criteria Crit)
95ace64403SRoman Lebedev         : Loc(SLoc), Nesting(CurrentNesting), C(Crit) {}
96ace64403SRoman Lebedev 
97ace64403SRoman Lebedev     // To minimize the sizeof(Detail), we only store the minimal info there.
98ace64403SRoman Lebedev     // This function is used to convert from the stored info into the usable
99ace64403SRoman Lebedev     // information - what message to output, how much of an increment did this
100ace64403SRoman Lebedev     // occurrence actually result in.
101ace64403SRoman Lebedev     std::pair<unsigned, unsigned short> process() const {
102ace64403SRoman Lebedev       assert(C != Criteria::None && "invalid criteria");
103ace64403SRoman Lebedev 
104cbdc3e1bSPiotr Zegar       unsigned MsgId = 0;           // The id of the message to output.
105cbdc3e1bSPiotr Zegar       unsigned short Increment = 0; // How much of an increment?
106ace64403SRoman Lebedev 
107ace64403SRoman Lebedev       if (C == Criteria::All) {
108ace64403SRoman Lebedev         Increment = 1 + Nesting;
109ace64403SRoman Lebedev         MsgId = 0;
110ace64403SRoman Lebedev       } else if (C == (Criteria::Increment | Criteria::IncrementNesting)) {
111ace64403SRoman Lebedev         Increment = 1;
112ace64403SRoman Lebedev         MsgId = 1;
113ace64403SRoman Lebedev       } else if (C == Criteria::Increment) {
114ace64403SRoman Lebedev         Increment = 1;
115ace64403SRoman Lebedev         MsgId = 2;
116ace64403SRoman Lebedev       } else if (C == Criteria::IncrementNesting) {
117ace64403SRoman Lebedev         Increment = 0; // Unused in this message.
118ace64403SRoman Lebedev         MsgId = 3;
119ace64403SRoman Lebedev       } else
120ace64403SRoman Lebedev         llvm_unreachable("should not get to here.");
121ace64403SRoman Lebedev 
122ace64403SRoman Lebedev       return std::make_pair(MsgId, Increment);
123ace64403SRoman Lebedev     }
124ace64403SRoman Lebedev   };
125ace64403SRoman Lebedev 
126ace64403SRoman Lebedev   // Limit of 25 is the "upstream"'s default.
127ace64403SRoman Lebedev   static constexpr unsigned DefaultLimit = 25U;
128ace64403SRoman Lebedev 
129*b77e4026SWang Qiang   // Based on the publicly-available numbers for some big open-source projects
130ace64403SRoman Lebedev   // https://sonarcloud.io/projects?languages=c%2Ccpp&size=5   we can estimate:
131ace64403SRoman Lebedev   // value ~20 would result in no allocs for 98% of functions, ~12 for 96%, ~10
132ace64403SRoman Lebedev   // for 91%, ~8 for 88%, ~6 for 84%, ~4 for 77%, ~2 for 64%, and ~1 for 37%.
133ace64403SRoman Lebedev   static_assert(sizeof(Detail) <= 8,
134ace64403SRoman Lebedev                 "Since we use SmallVector to minimize the amount of "
135ace64403SRoman Lebedev                 "allocations, we also need to consider the price we pay for "
136ace64403SRoman Lebedev                 "that in terms of stack usage. "
137ace64403SRoman Lebedev                 "Thus, it is good to minimize the size of the Detail struct.");
138ace64403SRoman Lebedev   SmallVector<Detail, DefaultLimit> Details; // 25 elements is 200 bytes.
139ace64403SRoman Lebedev   // Yes, 25 is a magic number. This is the seemingly-sane default for the
140ace64403SRoman Lebedev   // upper limit for function cognitive complexity. Thus it would make sense
141ace64403SRoman Lebedev   // to avoid allocations for any function that does not violate the limit.
142ace64403SRoman Lebedev 
143ace64403SRoman Lebedev   // The grand total Cognitive Complexity of the function.
144ace64403SRoman Lebedev   unsigned Total = 0;
145ace64403SRoman Lebedev 
146ace64403SRoman Lebedev   // The function used to store new increment, calculate the total complexity.
147ace64403SRoman Lebedev   void account(SourceLocation Loc, unsigned short Nesting, Criteria C);
148ace64403SRoman Lebedev };
149ace64403SRoman Lebedev 
150ace64403SRoman Lebedev // All the possible messages that can be output. The choice of the message
151ace64403SRoman Lebedev // to use is based of the combination of the CognitiveComplexity::Criteria.
152ace64403SRoman Lebedev // It would be nice to have it in CognitiveComplexity struct, but then it is
153ace64403SRoman Lebedev // not static.
154ace64403SRoman Lebedev static const std::array<const StringRef, 4> Msgs = {{
155ace64403SRoman Lebedev     // B1 + B2 + B3
156ace64403SRoman Lebedev     "+%0, including nesting penalty of %1, nesting level increased to %2",
157ace64403SRoman Lebedev 
158ace64403SRoman Lebedev     // B1 + B2
159ace64403SRoman Lebedev     "+%0, nesting level increased to %2",
160ace64403SRoman Lebedev 
161ace64403SRoman Lebedev     // B1
162ace64403SRoman Lebedev     "+%0",
163ace64403SRoman Lebedev 
164ace64403SRoman Lebedev     // B2
165ace64403SRoman Lebedev     "nesting level increased to %2",
166ace64403SRoman Lebedev }};
167ace64403SRoman Lebedev 
168ace64403SRoman Lebedev // Criteria is a bitset, thus a few helpers are needed.
169ace64403SRoman Lebedev CognitiveComplexity::Criteria operator|(CognitiveComplexity::Criteria LHS,
170ace64403SRoman Lebedev                                         CognitiveComplexity::Criteria RHS) {
171c60ac509SKazu Hirata   return static_cast<CognitiveComplexity::Criteria>(llvm::to_underlying(LHS) |
172c60ac509SKazu Hirata                                                     llvm::to_underlying(RHS));
173ace64403SRoman Lebedev }
174ace64403SRoman Lebedev CognitiveComplexity::Criteria operator&(CognitiveComplexity::Criteria LHS,
175ace64403SRoman Lebedev                                         CognitiveComplexity::Criteria RHS) {
176c60ac509SKazu Hirata   return static_cast<CognitiveComplexity::Criteria>(llvm::to_underlying(LHS) &
177c60ac509SKazu Hirata                                                     llvm::to_underlying(RHS));
178ace64403SRoman Lebedev }
179ace64403SRoman Lebedev CognitiveComplexity::Criteria &operator|=(CognitiveComplexity::Criteria &LHS,
180ace64403SRoman Lebedev                                           CognitiveComplexity::Criteria RHS) {
181ace64403SRoman Lebedev   LHS = operator|(LHS, RHS);
182ace64403SRoman Lebedev   return LHS;
183ace64403SRoman Lebedev }
184ace64403SRoman Lebedev CognitiveComplexity::Criteria &operator&=(CognitiveComplexity::Criteria &LHS,
185ace64403SRoman Lebedev                                           CognitiveComplexity::Criteria RHS) {
186ace64403SRoman Lebedev   LHS = operator&(LHS, RHS);
187ace64403SRoman Lebedev   return LHS;
188ace64403SRoman Lebedev }
189ace64403SRoman Lebedev 
190ace64403SRoman Lebedev void CognitiveComplexity::account(SourceLocation Loc, unsigned short Nesting,
191ace64403SRoman Lebedev                                   Criteria C) {
192ace64403SRoman Lebedev   C &= Criteria::All;
193ace64403SRoman Lebedev   assert(C != Criteria::None && "invalid criteria");
194ace64403SRoman Lebedev 
195ace64403SRoman Lebedev   Details.emplace_back(Loc, Nesting, C);
196ace64403SRoman Lebedev   const Detail &D = Details.back();
197ace64403SRoman Lebedev 
198cbdc3e1bSPiotr Zegar   unsigned MsgId = 0;
199cbdc3e1bSPiotr Zegar   unsigned short Increase = 0;
200ace64403SRoman Lebedev   std::tie(MsgId, Increase) = D.process();
201ace64403SRoman Lebedev 
202ace64403SRoman Lebedev   Total += Increase;
203ace64403SRoman Lebedev }
204ace64403SRoman Lebedev 
20511a411a4SPiotr Zegar class FunctionASTVisitor final
20611a411a4SPiotr Zegar     : public RecursiveASTVisitor<FunctionASTVisitor> {
20711a411a4SPiotr Zegar   using Base = RecursiveASTVisitor<FunctionASTVisitor>;
208ace64403SRoman Lebedev 
2098a944d82SJens Massberg   // If set to true, macros are ignored during analysis.
2108a944d82SJens Massberg   const bool IgnoreMacros;
2118a944d82SJens Massberg 
212ace64403SRoman Lebedev   // The current nesting level (increased by Criteria::IncrementNesting).
213ace64403SRoman Lebedev   unsigned short CurrentNestingLevel = 0;
214ace64403SRoman Lebedev 
215ace64403SRoman Lebedev   // Used to efficiently know the last type of the binary sequence operator
216ace64403SRoman Lebedev   // that was encountered. It would make sense for the function call to start
217ace64403SRoman Lebedev   // the new sequence, thus it is a stack.
218f71ffd3bSKazu Hirata   using OBO = std::optional<BinaryOperator::Opcode>;
219ace64403SRoman Lebedev   std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
220ace64403SRoman Lebedev 
221ace64403SRoman Lebedev public:
22211a411a4SPiotr Zegar   explicit FunctionASTVisitor(const bool IgnoreMacros)
2238883cb3eSAlexander Kornienko       : IgnoreMacros(IgnoreMacros) {}
2248a944d82SJens Massberg 
225ab2d3ce4SAlexander Kornienko   bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
226ace64403SRoman Lebedev     ++CurrentNestingLevel;
227ace64403SRoman Lebedev     bool ShouldContinue = Base::TraverseStmt(Node);
228ace64403SRoman Lebedev     --CurrentNestingLevel;
229ace64403SRoman Lebedev     return ShouldContinue;
230ace64403SRoman Lebedev   }
231ace64403SRoman Lebedev 
232ab2d3ce4SAlexander Kornienko   bool traverseDeclWithIncreasedNestingLevel(Decl *Node) {
233ace64403SRoman Lebedev     ++CurrentNestingLevel;
234ace64403SRoman Lebedev     bool ShouldContinue = Base::TraverseDecl(Node);
235ace64403SRoman Lebedev     --CurrentNestingLevel;
236ace64403SRoman Lebedev     return ShouldContinue;
237ace64403SRoman Lebedev   }
238ace64403SRoman Lebedev 
239ace64403SRoman Lebedev   bool TraverseIfStmt(IfStmt *Node, bool InElseIf = false) {
240ace64403SRoman Lebedev     if (!Node)
241ace64403SRoman Lebedev       return Base::TraverseIfStmt(Node);
242ace64403SRoman Lebedev 
243ace64403SRoman Lebedev     {
244cbdc3e1bSPiotr Zegar       CognitiveComplexity::Criteria Reasons =
245cbdc3e1bSPiotr Zegar           CognitiveComplexity::Criteria::None;
246ace64403SRoman Lebedev 
247ace64403SRoman Lebedev       // "If" increases cognitive complexity.
248ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::Increment;
249ace64403SRoman Lebedev       // "If" increases nesting level.
250ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
251ace64403SRoman Lebedev 
252ace64403SRoman Lebedev       if (!InElseIf) {
253ace64403SRoman Lebedev         // "If" receives a nesting increment commensurate with it's nested
254ace64403SRoman Lebedev         // depth, if it is not part of "else if".
255ace64403SRoman Lebedev         Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
256ace64403SRoman Lebedev       }
257ace64403SRoman Lebedev 
258ace64403SRoman Lebedev       CC.account(Node->getIfLoc(), CurrentNestingLevel, Reasons);
259ace64403SRoman Lebedev     }
260ace64403SRoman Lebedev 
261ace64403SRoman Lebedev     // If this IfStmt is *NOT* "else if", then only the body (i.e. "Then" and
262ace64403SRoman Lebedev     // "Else") is traversed with increased Nesting level.
263ace64403SRoman Lebedev     // However if this IfStmt *IS* "else if", then Nesting level is increased
264ace64403SRoman Lebedev     // for the whole IfStmt (i.e. for "Init", "Cond", "Then" and "Else").
265ace64403SRoman Lebedev 
266ace64403SRoman Lebedev     if (!InElseIf) {
267ace64403SRoman Lebedev       if (!TraverseStmt(Node->getInit()))
268ace64403SRoman Lebedev         return false;
269ace64403SRoman Lebedev 
270ace64403SRoman Lebedev       if (!TraverseStmt(Node->getCond()))
271ace64403SRoman Lebedev         return false;
272ace64403SRoman Lebedev     } else {
273ab2d3ce4SAlexander Kornienko       if (!traverseStmtWithIncreasedNestingLevel(Node->getInit()))
274ace64403SRoman Lebedev         return false;
275ace64403SRoman Lebedev 
276ab2d3ce4SAlexander Kornienko       if (!traverseStmtWithIncreasedNestingLevel(Node->getCond()))
277ace64403SRoman Lebedev         return false;
278ace64403SRoman Lebedev     }
279ace64403SRoman Lebedev 
280ace64403SRoman Lebedev     // "Then" always increases nesting level.
281ab2d3ce4SAlexander Kornienko     if (!traverseStmtWithIncreasedNestingLevel(Node->getThen()))
282ace64403SRoman Lebedev       return false;
283ace64403SRoman Lebedev 
284ace64403SRoman Lebedev     if (!Node->getElse())
285ace64403SRoman Lebedev       return true;
286ace64403SRoman Lebedev 
287ace64403SRoman Lebedev     if (auto *E = dyn_cast<IfStmt>(Node->getElse()))
288ace64403SRoman Lebedev       return TraverseIfStmt(E, true);
289ace64403SRoman Lebedev 
290ace64403SRoman Lebedev     {
291cbdc3e1bSPiotr Zegar       CognitiveComplexity::Criteria Reasons =
292cbdc3e1bSPiotr Zegar           CognitiveComplexity::Criteria::None;
293ace64403SRoman Lebedev 
294ace64403SRoman Lebedev       // "Else" increases cognitive complexity.
295ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::Increment;
296ace64403SRoman Lebedev       // "Else" increases nesting level.
297ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
298ace64403SRoman Lebedev       // "Else" DOES NOT receive a nesting increment commensurate with it's
299ace64403SRoman Lebedev       // nested depth.
300ace64403SRoman Lebedev 
301ace64403SRoman Lebedev       CC.account(Node->getElseLoc(), CurrentNestingLevel, Reasons);
302ace64403SRoman Lebedev     }
303ace64403SRoman Lebedev 
304ace64403SRoman Lebedev     // "Else" always increases nesting level.
305ab2d3ce4SAlexander Kornienko     return traverseStmtWithIncreasedNestingLevel(Node->getElse());
306ace64403SRoman Lebedev   }
307ace64403SRoman Lebedev 
308ace64403SRoman Lebedev // The currently-being-processed stack entry, which is always the top.
309ace64403SRoman Lebedev #define CurrentBinaryOperator BinaryOperatorsStack.top()
310ace64403SRoman Lebedev 
311ace64403SRoman Lebedev   // In a sequence of binary logical operators, if the new operator is different
312ace64403SRoman Lebedev   // from the previous one, then the cognitive complexity is increased.
313ace64403SRoman Lebedev   bool TraverseBinaryOperator(BinaryOperator *Op) {
314ace64403SRoman Lebedev     if (!Op || !Op->isLogicalOp())
315ace64403SRoman Lebedev       return Base::TraverseBinaryOperator(Op);
316ace64403SRoman Lebedev 
317ace64403SRoman Lebedev     // Make sure that there is always at least one frame in the stack.
318ace64403SRoman Lebedev     if (BinaryOperatorsStack.empty())
319ace64403SRoman Lebedev       BinaryOperatorsStack.emplace();
320ace64403SRoman Lebedev 
321ace64403SRoman Lebedev     // If this is the first binary operator that we are processing, or the
322ace64403SRoman Lebedev     // previous binary operator was different, there is an increment.
323ace64403SRoman Lebedev     if (!CurrentBinaryOperator || Op->getOpcode() != CurrentBinaryOperator)
324ace64403SRoman Lebedev       CC.account(Op->getOperatorLoc(), CurrentNestingLevel,
325ace64403SRoman Lebedev                  CognitiveComplexity::Criteria::Increment);
326ace64403SRoman Lebedev 
327ace64403SRoman Lebedev     // We might encounter a function call, which starts a new sequence, thus
328ace64403SRoman Lebedev     // we need to save the current previous binary operator.
329f71ffd3bSKazu Hirata     const std::optional<BinaryOperator::Opcode> BinOpCopy(
330f71ffd3bSKazu Hirata         CurrentBinaryOperator);
331ace64403SRoman Lebedev 
332ace64403SRoman Lebedev     // Record the operator that we are currently processing and traverse it.
333ace64403SRoman Lebedev     CurrentBinaryOperator = Op->getOpcode();
334ace64403SRoman Lebedev     bool ShouldContinue = Base::TraverseBinaryOperator(Op);
335ace64403SRoman Lebedev 
336ace64403SRoman Lebedev     // And restore the previous binary operator, which might be nonexistent.
337ace64403SRoman Lebedev     CurrentBinaryOperator = BinOpCopy;
338ace64403SRoman Lebedev 
339ace64403SRoman Lebedev     return ShouldContinue;
340ace64403SRoman Lebedev   }
341ace64403SRoman Lebedev 
342ace64403SRoman Lebedev   // It would make sense for the function call to start the new binary
343ace64403SRoman Lebedev   // operator sequence, thus let's make sure that it creates a new stack frame.
344ace64403SRoman Lebedev   bool TraverseCallExpr(CallExpr *Node) {
345ace64403SRoman Lebedev     // If we are not currently processing any binary operator sequence, then
346ace64403SRoman Lebedev     // no Node-handling is needed.
347ace64403SRoman Lebedev     if (!Node || BinaryOperatorsStack.empty() || !CurrentBinaryOperator)
348ace64403SRoman Lebedev       return Base::TraverseCallExpr(Node);
349ace64403SRoman Lebedev 
350ace64403SRoman Lebedev     // Else, do add [uninitialized] frame to the stack, and traverse call.
351ace64403SRoman Lebedev     BinaryOperatorsStack.emplace();
352ace64403SRoman Lebedev     bool ShouldContinue = Base::TraverseCallExpr(Node);
353ace64403SRoman Lebedev     // And remove the top frame.
354ace64403SRoman Lebedev     BinaryOperatorsStack.pop();
355ace64403SRoman Lebedev 
356ace64403SRoman Lebedev     return ShouldContinue;
357ace64403SRoman Lebedev   }
358ace64403SRoman Lebedev 
359ace64403SRoman Lebedev #undef CurrentBinaryOperator
360ace64403SRoman Lebedev 
361ace64403SRoman Lebedev   bool TraverseStmt(Stmt *Node) {
362ace64403SRoman Lebedev     if (!Node)
363ace64403SRoman Lebedev       return Base::TraverseStmt(Node);
364ace64403SRoman Lebedev 
3658a944d82SJens Massberg     if (IgnoreMacros && Node->getBeginLoc().isMacroID())
3668a944d82SJens Massberg       return true;
3678a944d82SJens Massberg 
368ace64403SRoman Lebedev     // Three following switch()'es have huge duplication, but it is better to
369ace64403SRoman Lebedev     // keep them separate, to simplify comparing them with the Specification.
370ace64403SRoman Lebedev 
371ace64403SRoman Lebedev     CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None;
372ace64403SRoman Lebedev     SourceLocation Location = Node->getBeginLoc();
373ace64403SRoman Lebedev 
374ace64403SRoman Lebedev     // B1. Increments
375ace64403SRoman Lebedev     // There is an increment for each of the following:
376ace64403SRoman Lebedev     switch (Node->getStmtClass()) {
377ace64403SRoman Lebedev     // if, else if, else are handled in TraverseIfStmt(),
378ace64403SRoman Lebedev     // FIXME: "each method in a recursion cycle" Increment is not implemented.
379ace64403SRoman Lebedev     case Stmt::ConditionalOperatorClass:
380ace64403SRoman Lebedev     case Stmt::SwitchStmtClass:
381ace64403SRoman Lebedev     case Stmt::ForStmtClass:
382ace64403SRoman Lebedev     case Stmt::CXXForRangeStmtClass:
383ace64403SRoman Lebedev     case Stmt::WhileStmtClass:
384ace64403SRoman Lebedev     case Stmt::DoStmtClass:
385ace64403SRoman Lebedev     case Stmt::CXXCatchStmtClass:
386ace64403SRoman Lebedev     case Stmt::GotoStmtClass:
387ace64403SRoman Lebedev     case Stmt::IndirectGotoStmtClass:
388ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::Increment;
389ace64403SRoman Lebedev       break;
390ace64403SRoman Lebedev     default:
391ace64403SRoman Lebedev       // break LABEL, continue LABEL increase cognitive complexity,
392ace64403SRoman Lebedev       // but they are not supported in C++ or C.
393ace64403SRoman Lebedev       // Regular break/continue do not increase cognitive complexity.
394ace64403SRoman Lebedev       break;
395ace64403SRoman Lebedev     }
396ace64403SRoman Lebedev 
397ace64403SRoman Lebedev     // B2. Nesting level
398ace64403SRoman Lebedev     // The following structures increment the nesting level:
399ace64403SRoman Lebedev     switch (Node->getStmtClass()) {
400ace64403SRoman Lebedev     // if, else if, else are handled in TraverseIfStmt(),
401ace64403SRoman Lebedev     // Nested methods and such are handled in TraverseDecl.
402ace64403SRoman Lebedev     case Stmt::ConditionalOperatorClass:
403ace64403SRoman Lebedev     case Stmt::SwitchStmtClass:
404ace64403SRoman Lebedev     case Stmt::ForStmtClass:
405ace64403SRoman Lebedev     case Stmt::CXXForRangeStmtClass:
406ace64403SRoman Lebedev     case Stmt::WhileStmtClass:
407ace64403SRoman Lebedev     case Stmt::DoStmtClass:
408ace64403SRoman Lebedev     case Stmt::CXXCatchStmtClass:
409ace64403SRoman Lebedev     case Stmt::LambdaExprClass:
410ace64403SRoman Lebedev     case Stmt::StmtExprClass:
411ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
412ace64403SRoman Lebedev       break;
413ace64403SRoman Lebedev     default:
414ace64403SRoman Lebedev       break;
415ace64403SRoman Lebedev     }
416ace64403SRoman Lebedev 
417ace64403SRoman Lebedev     // B3. Nesting increments
418ace64403SRoman Lebedev     // The following structures receive a nesting increment
419ace64403SRoman Lebedev     // commensurate with their nested depth inside B2 structures:
420ace64403SRoman Lebedev     switch (Node->getStmtClass()) {
421ace64403SRoman Lebedev     // if, else if, else are handled in TraverseIfStmt().
422ace64403SRoman Lebedev     case Stmt::ConditionalOperatorClass:
423ace64403SRoman Lebedev     case Stmt::SwitchStmtClass:
424ace64403SRoman Lebedev     case Stmt::ForStmtClass:
425ace64403SRoman Lebedev     case Stmt::CXXForRangeStmtClass:
426ace64403SRoman Lebedev     case Stmt::WhileStmtClass:
427ace64403SRoman Lebedev     case Stmt::DoStmtClass:
428ace64403SRoman Lebedev     case Stmt::CXXCatchStmtClass:
429ace64403SRoman Lebedev       Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
430ace64403SRoman Lebedev       break;
431ace64403SRoman Lebedev     default:
432ace64403SRoman Lebedev       break;
433ace64403SRoman Lebedev     }
434ace64403SRoman Lebedev 
435ace64403SRoman Lebedev     if (Node->getStmtClass() == Stmt::ConditionalOperatorClass) {
436ace64403SRoman Lebedev       // A little beautification.
437ace64403SRoman Lebedev       // For conditional operator "cond ? true : false" point at the "?"
438ace64403SRoman Lebedev       // symbol.
4399acba520SSimon Pilgrim       Location = cast<ConditionalOperator>(Node)->getQuestionLoc();
440ace64403SRoman Lebedev     }
441ace64403SRoman Lebedev 
442ace64403SRoman Lebedev     // If we have found any reasons, let's account it.
443ace64403SRoman Lebedev     if (Reasons & CognitiveComplexity::Criteria::All)
444ace64403SRoman Lebedev       CC.account(Location, CurrentNestingLevel, Reasons);
445ace64403SRoman Lebedev 
446ace64403SRoman Lebedev     // Did we decide that the nesting level should be increased?
447ace64403SRoman Lebedev     if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting))
448ace64403SRoman Lebedev       return Base::TraverseStmt(Node);
449ace64403SRoman Lebedev 
450ab2d3ce4SAlexander Kornienko     return traverseStmtWithIncreasedNestingLevel(Node);
451ace64403SRoman Lebedev   }
452ace64403SRoman Lebedev 
453ace64403SRoman Lebedev   // The parameter MainAnalyzedFunction is needed to differentiate between the
454ace64403SRoman Lebedev   // cases where TraverseDecl() is the entry point from
455ace64403SRoman Lebedev   // FunctionCognitiveComplexityCheck::check() and the cases where it was called
45611a411a4SPiotr Zegar   // from the FunctionASTVisitor itself. Explanation: if we get a function
45711a411a4SPiotr Zegar   // definition (e.g. constructor, destructor, method), the Cognitive Complexity
45811a411a4SPiotr Zegar   // specification states that the Nesting level shall be increased. But if this
45911a411a4SPiotr Zegar   // function is the entry point, then the Nesting level should not be
46011a411a4SPiotr Zegar   // increased. Thus that parameter is there and is used to fall-through
46111a411a4SPiotr Zegar   // directly to traversing if this is the main function that is being analyzed.
462ace64403SRoman Lebedev   bool TraverseDecl(Decl *Node, bool MainAnalyzedFunction = false) {
463ace64403SRoman Lebedev     if (!Node || MainAnalyzedFunction)
464ace64403SRoman Lebedev       return Base::TraverseDecl(Node);
465ace64403SRoman Lebedev 
466ace64403SRoman Lebedev     // B2. Nesting level
467ace64403SRoman Lebedev     // The following structures increment the nesting level:
468ace64403SRoman Lebedev     switch (Node->getKind()) {
469ace64403SRoman Lebedev     case Decl::Function:
470ace64403SRoman Lebedev     case Decl::CXXMethod:
471ace64403SRoman Lebedev     case Decl::CXXConstructor:
472ace64403SRoman Lebedev     case Decl::CXXDestructor:
473ace64403SRoman Lebedev     case Decl::Block:
474ace64403SRoman Lebedev       break;
475ace64403SRoman Lebedev     default:
476ace64403SRoman Lebedev       // If this is something else, we use early return!
477ace64403SRoman Lebedev       return Base::TraverseDecl(Node);
478ace64403SRoman Lebedev       break;
479ace64403SRoman Lebedev     }
480ace64403SRoman Lebedev 
481ace64403SRoman Lebedev     CC.account(Node->getBeginLoc(), CurrentNestingLevel,
482ace64403SRoman Lebedev                CognitiveComplexity::Criteria::IncrementNesting);
483ace64403SRoman Lebedev 
484ab2d3ce4SAlexander Kornienko     return traverseDeclWithIncreasedNestingLevel(Node);
485ace64403SRoman Lebedev   }
486ace64403SRoman Lebedev 
487ace64403SRoman Lebedev   CognitiveComplexity CC;
488ace64403SRoman Lebedev };
489ace64403SRoman Lebedev 
490ace64403SRoman Lebedev } // namespace
491ace64403SRoman Lebedev 
492ace64403SRoman Lebedev FunctionCognitiveComplexityCheck::FunctionCognitiveComplexityCheck(
493ace64403SRoman Lebedev     StringRef Name, ClangTidyContext *Context)
494ace64403SRoman Lebedev     : ClangTidyCheck(Name, Context),
495bff7faeaSJens Massberg       Threshold(Options.get("Threshold", CognitiveComplexity::DefaultLimit)),
4968a944d82SJens Massberg       DescribeBasicIncrements(Options.get("DescribeBasicIncrements", true)),
4978a944d82SJens Massberg       IgnoreMacros(Options.get("IgnoreMacros", false)) {}
498ace64403SRoman Lebedev 
499ace64403SRoman Lebedev void FunctionCognitiveComplexityCheck::storeOptions(
500ace64403SRoman Lebedev     ClangTidyOptions::OptionMap &Opts) {
501ace64403SRoman Lebedev   Options.store(Opts, "Threshold", Threshold);
502bff7faeaSJens Massberg   Options.store(Opts, "DescribeBasicIncrements", DescribeBasicIncrements);
5038a944d82SJens Massberg   Options.store(Opts, "IgnoreMacros", IgnoreMacros);
504ace64403SRoman Lebedev }
505ace64403SRoman Lebedev 
506ace64403SRoman Lebedev void FunctionCognitiveComplexityCheck::registerMatchers(MatchFinder *Finder) {
507ace64403SRoman Lebedev   Finder->addMatcher(
50832d565b4SZinovy Nis       functionDecl(isDefinition(),
5096852a29aSStephen Kelly                    unless(anyOf(isDefaulted(), isDeleted(), isWeak())))
510ace64403SRoman Lebedev           .bind("func"),
511ace64403SRoman Lebedev       this);
5126852a29aSStephen Kelly   Finder->addMatcher(lambdaExpr().bind("lambda"), this);
513ace64403SRoman Lebedev }
514ace64403SRoman Lebedev 
515ace64403SRoman Lebedev void FunctionCognitiveComplexityCheck::check(
516ace64403SRoman Lebedev     const MatchFinder::MatchResult &Result) {
517ace64403SRoman Lebedev 
51811a411a4SPiotr Zegar   FunctionASTVisitor Visitor(IgnoreMacros);
5196852a29aSStephen Kelly   SourceLocation Loc;
5206852a29aSStephen Kelly 
5216852a29aSStephen Kelly   const auto *TheDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
5226852a29aSStephen Kelly   const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
5236852a29aSStephen Kelly   if (TheDecl) {
5246852a29aSStephen Kelly     assert(TheDecl->hasBody() &&
5256852a29aSStephen Kelly            "The matchers should only match the functions that "
5266852a29aSStephen Kelly            "have user-provided body.");
5276852a29aSStephen Kelly     Loc = TheDecl->getLocation();
5286852a29aSStephen Kelly     Visitor.TraverseDecl(const_cast<FunctionDecl *>(TheDecl), true);
5296852a29aSStephen Kelly   } else {
5306852a29aSStephen Kelly     Loc = TheLambdaExpr->getBeginLoc();
5316852a29aSStephen Kelly     Visitor.TraverseLambdaExpr(const_cast<LambdaExpr *>(TheLambdaExpr));
5326852a29aSStephen Kelly   }
533ace64403SRoman Lebedev 
534ace64403SRoman Lebedev   if (Visitor.CC.Total <= Threshold)
535ace64403SRoman Lebedev     return;
536ace64403SRoman Lebedev 
5376852a29aSStephen Kelly   if (TheDecl)
5386852a29aSStephen Kelly     diag(Loc, "function %0 has cognitive complexity of %1 (threshold %2)")
5396852a29aSStephen Kelly         << TheDecl << Visitor.CC.Total << Threshold;
5406852a29aSStephen Kelly   else
5416852a29aSStephen Kelly     diag(Loc, "lambda has cognitive complexity of %0 (threshold %1)")
5426852a29aSStephen Kelly         << Visitor.CC.Total << Threshold;
543ace64403SRoman Lebedev 
544bff7faeaSJens Massberg   if (!DescribeBasicIncrements)
545bff7faeaSJens Massberg     return;
546bff7faeaSJens Massberg 
547ace64403SRoman Lebedev   // Output all the basic increments of complexity.
548ace64403SRoman Lebedev   for (const auto &Detail : Visitor.CC.Details) {
549cbdc3e1bSPiotr Zegar     unsigned MsgId = 0;          // The id of the message to output.
550cbdc3e1bSPiotr Zegar     unsigned short Increase = 0; // How much of an increment?
551ace64403SRoman Lebedev     std::tie(MsgId, Increase) = Detail.process();
552ace64403SRoman Lebedev     assert(MsgId < Msgs.size() && "MsgId should always be valid");
553ace64403SRoman Lebedev     // Increase, on the other hand, can be 0.
554ace64403SRoman Lebedev 
555ace64403SRoman Lebedev     diag(Detail.Loc, Msgs[MsgId], DiagnosticIDs::Note)
5561596cc83SRoman Lebedev         << (unsigned)Increase << (unsigned)Detail.Nesting << 1 + Detail.Nesting;
557ace64403SRoman Lebedev   }
558ace64403SRoman Lebedev }
559ace64403SRoman Lebedev 
5607d2ea6c4SCarlos Galvez } // namespace clang::tidy::readability
561