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/BitmaskEnum.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,
83 LLVM_MARK_AS_BITMASK_ENUM(PenalizeNesting),
89 const SourceLocation Loc;
90 const unsigned short Nesting;
93 Detail(SourceLocation SLoc,
unsigned short CurrentNesting, Criteria Crit)
94 : Loc(SLoc), Nesting(CurrentNesting), C(Crit) {}
100 std::pair<unsigned, unsigned short> process()
const {
101 assert(C != Criteria::None &&
"invalid criteria");
104 unsigned short Increment = 0;
106 if (C == Criteria::All) {
107 Increment = 1 + Nesting;
109 }
else if (C == (Criteria::Increment | Criteria::IncrementNesting)) {
112 }
else if (C == Criteria::Increment) {
115 }
else if (C == Criteria::IncrementNesting) {
119 llvm_unreachable(
"should not get to here.");
121 return {MsgId, Increment};
126 static constexpr unsigned DefaultLimit = 25U;
132 static_assert(
sizeof(Detail) <= 8,
133 "Since we use SmallVector to minimize the amount of "
134 "allocations, we also need to consider the price we pay for "
135 "that in terms of stack usage. "
136 "Thus, it is good to minimize the size of the Detail struct.");
137 SmallVector<Detail, DefaultLimit> Details;
146 void account(SourceLocation Loc,
unsigned short Nesting, Criteria C);
155static constexpr std::array<StringRef, 4>
Msgs = {{
157 "+%0, including nesting penalty of %1, nesting level increased to %2",
160 "+%0, nesting level increased to %2",
166 "nesting level increased to %2",
169void CognitiveComplexity::account(SourceLocation Loc,
unsigned short Nesting,
172 assert(C != Criteria::None &&
"invalid criteria");
174 Details.emplace_back(Loc, Nesting, C);
175 const Detail &D = Details.back();
178 unsigned short Increase = 0;
179 std::tie(MsgId, Increase) = D.process();
186class FunctionASTVisitor final
187 :
public RecursiveASTVisitor<FunctionASTVisitor> {
188 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
191 const bool IgnoreMacros;
194 unsigned short CurrentNestingLevel = 0;
199 using OBO = std::optional<BinaryOperator::Opcode>;
200 std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
203 explicit FunctionASTVisitor(
const bool IgnoreMacros)
204 : IgnoreMacros(IgnoreMacros) {}
206 bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
207 ++CurrentNestingLevel;
208 const bool ShouldContinue = Base::TraverseStmt(Node);
209 --CurrentNestingLevel;
210 return ShouldContinue;
213 bool traverseDeclWithIncreasedNestingLevel(Decl *Node) {
214 ++CurrentNestingLevel;
215 const bool ShouldContinue = Base::TraverseDecl(Node);
216 --CurrentNestingLevel;
217 return ShouldContinue;
220 bool TraverseIfStmt(IfStmt *Node,
bool InElseIf =
false) {
222 return Base::TraverseIfStmt(Node);
225 CognitiveComplexity::Criteria Reasons =
226 CognitiveComplexity::Criteria::None;
229 Reasons |= CognitiveComplexity::Criteria::Increment;
231 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
236 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
239 CC.account(Node->getIfLoc(), CurrentNestingLevel, Reasons);
248 if (!TraverseStmt(Node->getInit()))
251 if (!TraverseStmt(Node->getCond()))
254 if (!traverseStmtWithIncreasedNestingLevel(Node->getInit()))
257 if (!traverseStmtWithIncreasedNestingLevel(Node->getCond()))
262 if (!traverseStmtWithIncreasedNestingLevel(Node->getThen()))
265 if (!Node->getElse())
268 if (
auto *E = dyn_cast<IfStmt>(Node->getElse()))
269 return TraverseIfStmt(E,
true);
272 CognitiveComplexity::Criteria Reasons =
273 CognitiveComplexity::Criteria::None;
276 Reasons |= CognitiveComplexity::Criteria::Increment;
278 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
282 CC.account(Node->getElseLoc(), CurrentNestingLevel, Reasons);
286 return traverseStmtWithIncreasedNestingLevel(Node->getElse());
290#define CurrentBinaryOperator BinaryOperatorsStack.top()
294 bool TraverseBinaryOperator(BinaryOperator *Op) {
295 if (!Op || !Op->isLogicalOp())
296 return Base::TraverseBinaryOperator(Op);
299 if (BinaryOperatorsStack.empty())
300 BinaryOperatorsStack.emplace();
305 CC.account(Op->getOperatorLoc(), CurrentNestingLevel,
306 CognitiveComplexity::Criteria::Increment);
310 const std::optional<BinaryOperator::Opcode> BinOpCopy(
315 const bool ShouldContinue = Base::TraverseBinaryOperator(Op);
320 return ShouldContinue;
325 bool TraverseCallExpr(CallExpr *Node) {
329 return Base::TraverseCallExpr(Node);
332 BinaryOperatorsStack.emplace();
333 const bool ShouldContinue = Base::TraverseCallExpr(Node);
335 BinaryOperatorsStack.pop();
337 return ShouldContinue;
340#undef CurrentBinaryOperator
342 bool TraverseStmt(Stmt *Node) {
344 return Base::TraverseStmt(Node);
346 if (IgnoreMacros && Node->getBeginLoc().isMacroID())
352 CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None;
353 SourceLocation Location = Node->getBeginLoc();
357 switch (Node->getStmtClass()) {
360 case Stmt::ConditionalOperatorClass:
361 case Stmt::SwitchStmtClass:
362 case Stmt::ForStmtClass:
363 case Stmt::CXXForRangeStmtClass:
364 case Stmt::WhileStmtClass:
365 case Stmt::DoStmtClass:
366 case Stmt::CXXCatchStmtClass:
367 case Stmt::GotoStmtClass:
368 case Stmt::IndirectGotoStmtClass:
369 Reasons |= CognitiveComplexity::Criteria::Increment;
380 switch (Node->getStmtClass()) {
383 case Stmt::ConditionalOperatorClass:
384 case Stmt::SwitchStmtClass:
385 case Stmt::ForStmtClass:
386 case Stmt::CXXForRangeStmtClass:
387 case Stmt::WhileStmtClass:
388 case Stmt::DoStmtClass:
389 case Stmt::CXXCatchStmtClass:
390 case Stmt::LambdaExprClass:
391 case Stmt::StmtExprClass:
392 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
401 switch (Node->getStmtClass()) {
403 case Stmt::ConditionalOperatorClass:
404 case Stmt::SwitchStmtClass:
405 case Stmt::ForStmtClass:
406 case Stmt::CXXForRangeStmtClass:
407 case Stmt::WhileStmtClass:
408 case Stmt::DoStmtClass:
409 case Stmt::CXXCatchStmtClass:
410 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
416 if (Node->getStmtClass() == Stmt::ConditionalOperatorClass) {
420 Location = cast<ConditionalOperator>(Node)->getQuestionLoc();
424 if (Reasons & CognitiveComplexity::Criteria::All)
425 CC.account(Location, CurrentNestingLevel, Reasons);
428 if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting))
429 return Base::TraverseStmt(Node);
431 return traverseStmtWithIncreasedNestingLevel(Node);
443 bool TraverseDecl(Decl *Node,
bool MainAnalyzedFunction =
false) {
444 if (!Node || MainAnalyzedFunction)
445 return Base::TraverseDecl(Node);
449 switch (Node->getKind()) {
451 case Decl::CXXMethod:
452 case Decl::CXXConstructor:
453 case Decl::CXXDestructor:
458 return Base::TraverseDecl(Node);
462 CC.account(Node->getBeginLoc(), CurrentNestingLevel,
463 CognitiveComplexity::Criteria::IncrementNesting);
465 return traverseDeclWithIncreasedNestingLevel(Node);
468 CognitiveComplexity CC;
476 Threshold(Options.get(
"Threshold", CognitiveComplexity::DefaultLimit)),
477 DescribeBasicIncrements(Options.get(
"DescribeBasicIncrements", true)),
478 IgnoreMacros(Options.get(
"IgnoreMacros", false)) {}
482 Options.store(Opts,
"Threshold", Threshold);
483 Options.store(Opts,
"DescribeBasicIncrements", DescribeBasicIncrements);
484 Options.store(Opts,
"IgnoreMacros", IgnoreMacros);
489 functionDecl(isDefinition(),
490 unless(anyOf(isDefaulted(), isDeleted(), isWeak())))
493 Finder->addMatcher(lambdaExpr().bind(
"lambda"),
this);
497 const MatchFinder::MatchResult &Result) {
498 FunctionASTVisitor Visitor(IgnoreMacros);
501 const auto *TheDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"func");
502 const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>(
"lambda");
504 assert(TheDecl->hasBody() &&
505 "The matchers should only match the functions that "
506 "have user-provided body.");
507 Loc = TheDecl->getLocation();
508 Visitor.TraverseDecl(
const_cast<FunctionDecl *
>(TheDecl),
true);
510 Loc = TheLambdaExpr->getBeginLoc();
511 Visitor.TraverseLambdaExpr(
const_cast<LambdaExpr *
>(TheLambdaExpr));
514 if (Visitor.CC.Total <= Threshold)
518 diag(Loc,
"function %0 has cognitive complexity of %1 (threshold %2)")
519 << TheDecl << Visitor.CC.Total << Threshold;
521 diag(Loc,
"lambda has cognitive complexity of %0 (threshold %1)")
522 << Visitor.CC.Total << Threshold;
524 if (!DescribeBasicIncrements)
528 for (
const auto &Detail : Visitor.CC.Details) {
530 unsigned short Increase = 0;
531 std::tie(MsgId, Increase) = Detail.process();
532 assert(MsgId <
Msgs.size() &&
"MsgId should always be valid");
535 diag(Detail.Loc,
Msgs[MsgId], DiagnosticIDs::Note)
536 << Increase << 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 constexpr std::array< StringRef, 4 > Msgs
llvm::StringMap< ClangTidyValue > OptionMap