11#include "clang/AST/Decl.h"
12#include "clang/AST/DeclBase.h"
13#include "clang/AST/Expr.h"
14#include "clang/AST/RecursiveASTVisitor.h"
15#include "clang/AST/Stmt.h"
16#include "clang/ASTMatchers/ASTMatchFinder.h"
17#include "clang/ASTMatchers/ASTMatchers.h"
18#include "clang/ASTMatchers/ASTMatchersInternal.h"
19#include "clang/Basic/Diagnostic.h"
20#include "clang/Basic/DiagnosticIDs.h"
21#include "clang/Basic/LLVM.h"
22#include "clang/Basic/SourceLocation.h"
23#include "llvm/ADT/STLForwardCompat.h"
24#include "llvm/Support/ErrorHandling.h"
37struct CognitiveComplexity final {
44 enum Criteria : uint8_t {
69 IncrementNesting = 1U << 1,
79 PenalizeNesting = 1U << 2,
81 All = Increment | PenalizeNesting | IncrementNesting,
87 const SourceLocation Loc;
88 const unsigned short Nesting;
91 Detail(SourceLocation SLoc,
unsigned short CurrentNesting, Criteria Crit)
92 : Loc(SLoc), Nesting(CurrentNesting), C(Crit) {}
98 std::pair<unsigned, unsigned short> process()
const {
99 assert(C != Criteria::None &&
"invalid criteria");
102 unsigned short Increment = 0;
104 if (C == Criteria::All) {
105 Increment = 1 + Nesting;
107 }
else if (C == (Criteria::Increment | Criteria::IncrementNesting)) {
110 }
else if (C == Criteria::Increment) {
113 }
else if (C == Criteria::IncrementNesting) {
117 llvm_unreachable(
"should not get to here.");
119 return std::make_pair(MsgId, Increment);
124 static constexpr unsigned DefaultLimit = 25U;
130 static_assert(
sizeof(Detail) <= 8,
131 "Since we use SmallVector to minimize the amount of "
132 "allocations, we also need to consider the price we pay for "
133 "that in terms of stack usage. "
134 "Thus, it is good to minimize the size of the Detail struct.");
135 SmallVector<Detail, DefaultLimit> Details;
144 void account(SourceLocation Loc,
unsigned short Nesting, Criteria C);
153static const std::array<const StringRef, 4>
Msgs = {{
155 "+%0, including nesting penalty of %1, nesting level increased to %2",
158 "+%0, nesting level increased to %2",
164 "nesting level increased to %2",
168static CognitiveComplexity::Criteria
170 CognitiveComplexity::Criteria RHS) {
171 return static_cast<CognitiveComplexity::Criteria
>(llvm::to_underlying(LHS) |
172 llvm::to_underlying(RHS));
174static CognitiveComplexity::Criteria
176 CognitiveComplexity::Criteria RHS) {
177 return static_cast<CognitiveComplexity::Criteria
>(llvm::to_underlying(LHS) &
178 llvm::to_underlying(RHS));
180static CognitiveComplexity::Criteria &
182 CognitiveComplexity::Criteria RHS) {
186static CognitiveComplexity::Criteria &
188 CognitiveComplexity::Criteria RHS) {
193void CognitiveComplexity::account(SourceLocation Loc,
unsigned short Nesting,
196 assert(C != Criteria::None &&
"invalid criteria");
198 Details.emplace_back(Loc, Nesting, C);
199 const Detail &D = Details.back();
202 unsigned short Increase = 0;
203 std::tie(MsgId, Increase) = D.process();
210class FunctionASTVisitor final
211 :
public RecursiveASTVisitor<FunctionASTVisitor> {
212 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
215 const bool IgnoreMacros;
218 unsigned short CurrentNestingLevel = 0;
223 using OBO = std::optional<BinaryOperator::Opcode>;
224 std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
227 explicit FunctionASTVisitor(
const bool IgnoreMacros)
228 : IgnoreMacros(IgnoreMacros) {}
230 bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
231 ++CurrentNestingLevel;
232 bool ShouldContinue = Base::TraverseStmt(Node);
233 --CurrentNestingLevel;
234 return ShouldContinue;
237 bool traverseDeclWithIncreasedNestingLevel(Decl *Node) {
238 ++CurrentNestingLevel;
239 bool ShouldContinue = Base::TraverseDecl(Node);
240 --CurrentNestingLevel;
241 return ShouldContinue;
244 bool TraverseIfStmt(IfStmt *Node,
bool InElseIf =
false) {
246 return Base::TraverseIfStmt(Node);
249 CognitiveComplexity::Criteria Reasons =
250 CognitiveComplexity::Criteria::None;
253 Reasons |= CognitiveComplexity::Criteria::Increment;
255 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
260 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
263 CC.account(Node->getIfLoc(), CurrentNestingLevel, Reasons);
272 if (!TraverseStmt(Node->getInit()))
275 if (!TraverseStmt(Node->getCond()))
278 if (!traverseStmtWithIncreasedNestingLevel(Node->getInit()))
281 if (!traverseStmtWithIncreasedNestingLevel(Node->getCond()))
286 if (!traverseStmtWithIncreasedNestingLevel(Node->getThen()))
289 if (!Node->getElse())
292 if (
auto *E = dyn_cast<IfStmt>(Node->getElse()))
293 return TraverseIfStmt(E,
true);
296 CognitiveComplexity::Criteria Reasons =
297 CognitiveComplexity::Criteria::None;
300 Reasons |= CognitiveComplexity::Criteria::Increment;
302 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
306 CC.account(Node->getElseLoc(), CurrentNestingLevel, Reasons);
310 return traverseStmtWithIncreasedNestingLevel(Node->getElse());
314#define CurrentBinaryOperator BinaryOperatorsStack.top()
318 bool TraverseBinaryOperator(BinaryOperator *Op) {
319 if (!Op || !Op->isLogicalOp())
320 return Base::TraverseBinaryOperator(Op);
323 if (BinaryOperatorsStack.empty())
324 BinaryOperatorsStack.emplace();
329 CC.account(Op->getOperatorLoc(), CurrentNestingLevel,
330 CognitiveComplexity::Criteria::Increment);
334 const std::optional<BinaryOperator::Opcode> BinOpCopy(
339 bool ShouldContinue = Base::TraverseBinaryOperator(Op);
344 return ShouldContinue;
349 bool TraverseCallExpr(CallExpr *Node) {
353 return Base::TraverseCallExpr(Node);
356 BinaryOperatorsStack.emplace();
357 bool ShouldContinue = Base::TraverseCallExpr(Node);
359 BinaryOperatorsStack.pop();
361 return ShouldContinue;
364#undef CurrentBinaryOperator
366 bool TraverseStmt(Stmt *Node) {
368 return Base::TraverseStmt(Node);
370 if (IgnoreMacros && Node->getBeginLoc().isMacroID())
376 CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None;
377 SourceLocation Location = Node->getBeginLoc();
381 switch (Node->getStmtClass()) {
384 case Stmt::ConditionalOperatorClass:
385 case Stmt::SwitchStmtClass:
386 case Stmt::ForStmtClass:
387 case Stmt::CXXForRangeStmtClass:
388 case Stmt::WhileStmtClass:
389 case Stmt::DoStmtClass:
390 case Stmt::CXXCatchStmtClass:
391 case Stmt::GotoStmtClass:
392 case Stmt::IndirectGotoStmtClass:
393 Reasons |= CognitiveComplexity::Criteria::Increment;
404 switch (Node->getStmtClass()) {
407 case Stmt::ConditionalOperatorClass:
408 case Stmt::SwitchStmtClass:
409 case Stmt::ForStmtClass:
410 case Stmt::CXXForRangeStmtClass:
411 case Stmt::WhileStmtClass:
412 case Stmt::DoStmtClass:
413 case Stmt::CXXCatchStmtClass:
414 case Stmt::LambdaExprClass:
415 case Stmt::StmtExprClass:
416 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
425 switch (Node->getStmtClass()) {
427 case Stmt::ConditionalOperatorClass:
428 case Stmt::SwitchStmtClass:
429 case Stmt::ForStmtClass:
430 case Stmt::CXXForRangeStmtClass:
431 case Stmt::WhileStmtClass:
432 case Stmt::DoStmtClass:
433 case Stmt::CXXCatchStmtClass:
434 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
440 if (Node->getStmtClass() == Stmt::ConditionalOperatorClass) {
444 Location = cast<ConditionalOperator>(Node)->getQuestionLoc();
448 if (Reasons & CognitiveComplexity::Criteria::All)
449 CC.account(Location, CurrentNestingLevel, Reasons);
452 if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting))
453 return Base::TraverseStmt(Node);
455 return traverseStmtWithIncreasedNestingLevel(Node);
467 bool TraverseDecl(Decl *Node,
bool MainAnalyzedFunction =
false) {
468 if (!Node || MainAnalyzedFunction)
469 return Base::TraverseDecl(Node);
473 switch (Node->getKind()) {
475 case Decl::CXXMethod:
476 case Decl::CXXConstructor:
477 case Decl::CXXDestructor:
482 return Base::TraverseDecl(Node);
486 CC.account(Node->getBeginLoc(), CurrentNestingLevel,
487 CognitiveComplexity::Criteria::IncrementNesting);
489 return traverseDeclWithIncreasedNestingLevel(Node);
492 CognitiveComplexity CC;
500 Threshold(Options.get(
"Threshold", CognitiveComplexity::DefaultLimit)),
501 DescribeBasicIncrements(Options.get(
"DescribeBasicIncrements", true)),
502 IgnoreMacros(Options.get(
"IgnoreMacros", false)) {}
506 Options.store(Opts,
"Threshold", Threshold);
507 Options.store(Opts,
"DescribeBasicIncrements", DescribeBasicIncrements);
508 Options.store(Opts,
"IgnoreMacros", IgnoreMacros);
513 functionDecl(isDefinition(),
514 unless(anyOf(isDefaulted(), isDeleted(), isWeak())))
517 Finder->addMatcher(lambdaExpr().bind(
"lambda"),
this);
521 const MatchFinder::MatchResult &Result) {
523 FunctionASTVisitor Visitor(IgnoreMacros);
526 const auto *TheDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"func");
527 const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>(
"lambda");
529 assert(TheDecl->hasBody() &&
530 "The matchers should only match the functions that "
531 "have user-provided body.");
532 Loc = TheDecl->getLocation();
533 Visitor.TraverseDecl(
const_cast<FunctionDecl *
>(TheDecl),
true);
535 Loc = TheLambdaExpr->getBeginLoc();
536 Visitor.TraverseLambdaExpr(
const_cast<LambdaExpr *
>(TheLambdaExpr));
539 if (Visitor.CC.Total <= Threshold)
543 diag(Loc,
"function %0 has cognitive complexity of %1 (threshold %2)")
544 << TheDecl << Visitor.CC.Total << Threshold;
546 diag(Loc,
"lambda has cognitive complexity of %0 (threshold %1)")
547 << Visitor.CC.Total << Threshold;
549 if (!DescribeBasicIncrements)
553 for (
const auto &Detail : Visitor.CC.Details) {
555 unsigned short Increase = 0;
556 std::tie(MsgId, Increase) = Detail.process();
557 assert(MsgId <
Msgs.size() &&
"MsgId should always be valid");
560 diag(Detail.Loc,
Msgs[MsgId], DiagnosticIDs::Note)
561 << (unsigned)Increase << (
unsigned)Detail.Nesting << 1 + Detail.Nesting;
#define CurrentBinaryOperator
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
FunctionCognitiveComplexityCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
static const std::array< const StringRef, 4 > Msgs
static CognitiveComplexity::Criteria operator|(CognitiveComplexity::Criteria LHS, CognitiveComplexity::Criteria RHS)
static CognitiveComplexity::Criteria & operator&=(CognitiveComplexity::Criteria &LHS, CognitiveComplexity::Criteria RHS)
static CognitiveComplexity::Criteria & operator|=(CognitiveComplexity::Criteria &LHS, CognitiveComplexity::Criteria RHS)
static CognitiveComplexity::Criteria operator&(CognitiveComplexity::Criteria LHS, CognitiveComplexity::Criteria RHS)
llvm::StringMap< ClangTidyValue > OptionMap