10#include "../ClangTidyDiagnosticConsumer.h"
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/SmallVector.h"
24#include "llvm/Support/Casting.h"
25#include "llvm/Support/ErrorHandling.h"
39struct CognitiveComplexity final {
46 enum Criteria : uint8_t {
71 IncrementNesting = 1U << 1,
81 PenalizeNesting = 1U << 2,
83 All = Increment | PenalizeNesting | IncrementNesting,
89 const SourceLocation
Loc;
93 Detail(SourceLocation SLoc,
unsigned short CurrentNesting, Criteria Crit)
100 std::pair<unsigned, unsigned short> process()
const {
101 assert(C != Criteria::None &&
"invalid criteria");
104 unsigned short Increment;
106 if (C == Criteria::All) {
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 std::make_pair(MsgId, Increment);
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.");
146 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",
168CognitiveComplexity::Criteria
operator|(CognitiveComplexity::Criteria LHS,
169 CognitiveComplexity::Criteria RHS) {
170 return static_cast<CognitiveComplexity::Criteria
>(
171 static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>
>(LHS) |
172 static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>
>(RHS));
174CognitiveComplexity::Criteria
operator&(CognitiveComplexity::Criteria LHS,
175 CognitiveComplexity::Criteria RHS) {
176 return static_cast<CognitiveComplexity::Criteria
>(
177 static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>
>(LHS) &
178 static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>
>(RHS));
180CognitiveComplexity::Criteria &
operator|=(CognitiveComplexity::Criteria &LHS,
181 CognitiveComplexity::Criteria RHS) {
185CognitiveComplexity::Criteria &operator&=(CognitiveComplexity::Criteria &LHS,
186 CognitiveComplexity::Criteria RHS) {
191void CognitiveComplexity::account(SourceLocation
Loc,
unsigned short Nesting,
194 assert(
C != Criteria::None &&
"invalid criteria");
201 std::tie(MsgId, Increase) =
D.process();
206class FunctionASTVisitor final
211 const bool IgnoreMacros;
214 unsigned short CurrentNestingLevel = 0;
219 using OBO = std::optional<BinaryOperator::Opcode>;
220 std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
223 explicit FunctionASTVisitor(
const bool IgnoreMacros)
224 : IgnoreMacros(IgnoreMacros) {}
226 bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
227 ++CurrentNestingLevel;
228 bool ShouldContinue = Base::TraverseStmt(Node);
229 --CurrentNestingLevel;
230 return ShouldContinue;
233 bool traverseDeclWithIncreasedNestingLevel(
Decl *Node) {
234 ++CurrentNestingLevel;
235 bool ShouldContinue = Base::TraverseDecl(Node);
236 --CurrentNestingLevel;
237 return ShouldContinue;
240 bool TraverseIfStmt(IfStmt *Node,
bool InElseIf =
false) {
242 return Base::TraverseIfStmt(Node);
245 CognitiveComplexity::Criteria Reasons;
247 Reasons = CognitiveComplexity::Criteria::None;
250 Reasons |= CognitiveComplexity::Criteria::Increment;
252 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
257 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
260 CC.account(Node->getIfLoc(), CurrentNestingLevel, Reasons);
269 if (!TraverseStmt(Node->getInit()))
272 if (!TraverseStmt(Node->getCond()))
275 if (!traverseStmtWithIncreasedNestingLevel(Node->getInit()))
278 if (!traverseStmtWithIncreasedNestingLevel(Node->getCond()))
283 if (!traverseStmtWithIncreasedNestingLevel(Node->getThen()))
286 if (!Node->getElse())
289 if (
auto *
E = dyn_cast<IfStmt>(Node->getElse()))
290 return TraverseIfStmt(
E,
true);
293 CognitiveComplexity::Criteria Reasons;
295 Reasons = CognitiveComplexity::Criteria::None;
298 Reasons |= CognitiveComplexity::Criteria::Increment;
300 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
304 CC.account(Node->getElseLoc(), CurrentNestingLevel, Reasons);
308 return traverseStmtWithIncreasedNestingLevel(Node->getElse());
312#define CurrentBinaryOperator BinaryOperatorsStack.top()
316 bool TraverseBinaryOperator(BinaryOperator *Op) {
317 if (!Op || !Op->isLogicalOp())
318 return Base::TraverseBinaryOperator(Op);
321 if (BinaryOperatorsStack.empty())
322 BinaryOperatorsStack.emplace();
327 CC.account(Op->getOperatorLoc(), CurrentNestingLevel,
328 CognitiveComplexity::Criteria::Increment);
332 const std::optional<BinaryOperator::Opcode> BinOpCopy(
337 bool ShouldContinue = Base::TraverseBinaryOperator(Op);
342 return ShouldContinue;
347 bool TraverseCallExpr(CallExpr *Node) {
351 return Base::TraverseCallExpr(Node);
354 BinaryOperatorsStack.emplace();
355 bool ShouldContinue = Base::TraverseCallExpr(Node);
357 BinaryOperatorsStack.pop();
359 return ShouldContinue;
362#undef CurrentBinaryOperator
364 bool TraverseStmt(Stmt *Node) {
366 return Base::TraverseStmt(Node);
368 if (IgnoreMacros && Node->getBeginLoc().isMacroID())
374 CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None;
375 SourceLocation
Location = Node->getBeginLoc();
379 switch (Node->getStmtClass()) {
382 case Stmt::ConditionalOperatorClass:
383 case Stmt::SwitchStmtClass:
384 case Stmt::ForStmtClass:
385 case Stmt::CXXForRangeStmtClass:
386 case Stmt::WhileStmtClass:
387 case Stmt::DoStmtClass:
388 case Stmt::CXXCatchStmtClass:
389 case Stmt::GotoStmtClass:
390 case Stmt::IndirectGotoStmtClass:
391 Reasons |= CognitiveComplexity::Criteria::Increment;
402 switch (Node->getStmtClass()) {
405 case Stmt::ConditionalOperatorClass:
406 case Stmt::SwitchStmtClass:
407 case Stmt::ForStmtClass:
408 case Stmt::CXXForRangeStmtClass:
409 case Stmt::WhileStmtClass:
410 case Stmt::DoStmtClass:
411 case Stmt::CXXCatchStmtClass:
412 case Stmt::LambdaExprClass:
413 case Stmt::StmtExprClass:
414 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
423 switch (Node->getStmtClass()) {
425 case Stmt::ConditionalOperatorClass:
426 case Stmt::SwitchStmtClass:
427 case Stmt::ForStmtClass:
428 case Stmt::CXXForRangeStmtClass:
429 case Stmt::WhileStmtClass:
430 case Stmt::DoStmtClass:
431 case Stmt::CXXCatchStmtClass:
432 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
438 if (Node->getStmtClass() == Stmt::ConditionalOperatorClass) {
442 Location = cast<ConditionalOperator>(Node)->getQuestionLoc();
446 if (Reasons & CognitiveComplexity::Criteria::All)
447 CC.account(
Location, CurrentNestingLevel, Reasons);
450 if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting))
451 return Base::TraverseStmt(Node);
453 return traverseStmtWithIncreasedNestingLevel(Node);
465 bool TraverseDecl(
Decl *Node,
bool MainAnalyzedFunction =
false) {
466 if (!Node || MainAnalyzedFunction)
467 return Base::TraverseDecl(Node);
471 switch (Node->getKind()) {
473 case Decl::CXXMethod:
474 case Decl::CXXConstructor:
475 case Decl::CXXDestructor:
480 return Base::TraverseDecl(Node);
484 CC.account(Node->getBeginLoc(), CurrentNestingLevel,
485 CognitiveComplexity::Criteria::IncrementNesting);
487 return traverseDeclWithIncreasedNestingLevel(Node);
490 CognitiveComplexity
CC;
498 Threshold(Options.get(
"Threshold", CognitiveComplexity::
DefaultLimit)),
499 DescribeBasicIncrements(Options.get(
"DescribeBasicIncrements", true)),
500 IgnoreMacros(Options.get(
"IgnoreMacros", false)) {}
505 Options.
store(Opts,
"DescribeBasicIncrements", DescribeBasicIncrements);
511 functionDecl(isDefinition(),
512 unless(anyOf(isDefaulted(), isDeleted(), isWeak())))
515 Finder->addMatcher(lambdaExpr().bind(
"lambda"),
this);
519 const MatchFinder::MatchResult &Result) {
521 FunctionASTVisitor Visitor(IgnoreMacros);
524 const auto *
TheDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"func");
525 const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>(
"lambda");
528 "The matchers should only match the functions that "
529 "have user-provided body.");
531 Visitor.TraverseDecl(
const_cast<FunctionDecl *
>(
TheDecl),
true);
533 Loc = TheLambdaExpr->getBeginLoc();
534 Visitor.TraverseLambdaExpr(
const_cast<LambdaExpr *
>(TheLambdaExpr));
537 if (Visitor.CC.Total <= Threshold)
541 diag(
Loc,
"function %0 has cognitive complexity of %1 (threshold %2)")
542 <<
TheDecl << Visitor.CC.Total << Threshold;
544 diag(
Loc,
"lambda has cognitive complexity of %0 (threshold %1)")
545 << Visitor.CC.Total << Threshold;
547 if (!DescribeBasicIncrements)
551 for (
const auto &Detail : Visitor.CC.Details) {
553 unsigned short Increase;
554 std::tie(MsgId, Increase) = Detail.process();
555 assert(MsgId < Msgs.size() &&
"MsgId should always be valid");
558 diag(Detail.Loc, Msgs[MsgId], DiagnosticIDs::Note)
559 << (unsigned)Increase << (
unsigned)Detail.Nesting << 1 + Detail.Nesting;
const FunctionDecl * Decl
#define CurrentBinaryOperator
static constexpr unsigned DefaultLimit
const unsigned short Nesting
SmallVector< Detail, DefaultLimit > Details
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
FunctionCognitiveComplexityCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
DeclRelationSet operator&(DeclRelation L, DeclRelation R)
DeclRelationSet operator|(DeclRelation L, DeclRelation R)
IncludeGraphNode::SourceFlag & operator|=(IncludeGraphNode::SourceFlag &A, IncludeGraphNode::SourceFlag B)
@ None
Mix between the two parameters is not possible.
@ All
Only model a unidirectional implicit conversion and within it only one standard conversion sequence.
llvm::StringMap< ClangTidyValue > OptionMap