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