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/STLForwardCompat.h"
24#include "llvm/ADT/SmallVector.h"
25#include "llvm/Support/Casting.h"
26#include "llvm/Support/ErrorHandling.h"
40struct CognitiveComplexity final {
47 enum Criteria : uint8_t {
72 IncrementNesting = 1U << 1,
82 PenalizeNesting = 1U << 2,
84 All = Increment | PenalizeNesting | IncrementNesting,
90 const SourceLocation
Loc;
94 Detail(SourceLocation SLoc,
unsigned short CurrentNesting, Criteria Crit)
101 std::pair<unsigned, unsigned short> process()
const {
102 assert(C != Criteria::None &&
"invalid criteria");
105 unsigned short Increment = 0;
107 if (C == Criteria::All) {
110 }
else if (C == (Criteria::Increment | Criteria::IncrementNesting)) {
113 }
else if (C == Criteria::Increment) {
116 }
else if (C == Criteria::IncrementNesting) {
120 llvm_unreachable(
"should not get to here.");
122 return std::make_pair(MsgId, Increment);
133 static_assert(
sizeof(Detail) <= 8,
134 "Since we use SmallVector to minimize the amount of "
135 "allocations, we also need to consider the price we pay for "
136 "that in terms of stack usage. "
137 "Thus, it is good to minimize the size of the Detail struct.");
147 void account(SourceLocation
Loc,
unsigned short Nesting, Criteria
C);
154static const std::array<const StringRef, 4> Msgs = {{
156 "+%0, including nesting penalty of %1, nesting level increased to %2",
159 "+%0, nesting level increased to %2",
165 "nesting level increased to %2",
169CognitiveComplexity::Criteria
operator|(CognitiveComplexity::Criteria LHS,
170 CognitiveComplexity::Criteria RHS) {
171 return static_cast<CognitiveComplexity::Criteria
>(llvm::to_underlying(LHS) |
172 llvm::to_underlying(RHS));
174CognitiveComplexity::Criteria
operator&(CognitiveComplexity::Criteria LHS,
175 CognitiveComplexity::Criteria RHS) {
176 return static_cast<CognitiveComplexity::Criteria
>(llvm::to_underlying(LHS) &
177 llvm::to_underlying(RHS));
179CognitiveComplexity::Criteria &
operator|=(CognitiveComplexity::Criteria &LHS,
180 CognitiveComplexity::Criteria RHS) {
184CognitiveComplexity::Criteria &operator&=(CognitiveComplexity::Criteria &LHS,
185 CognitiveComplexity::Criteria RHS) {
190void CognitiveComplexity::account(SourceLocation
Loc,
unsigned short Nesting,
193 assert(
C != Criteria::None &&
"invalid criteria");
196 const Detail &D =
Details.back();
200 std::tie(MsgId, Increase) = D.process();
205class FunctionASTVisitor final
210 const bool IgnoreMacros;
213 unsigned short CurrentNestingLevel = 0;
218 using OBO = std::optional<BinaryOperator::Opcode>;
219 std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
222 explicit FunctionASTVisitor(
const bool IgnoreMacros)
223 : IgnoreMacros(IgnoreMacros) {}
225 bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
226 ++CurrentNestingLevel;
227 bool ShouldContinue = Base::TraverseStmt(Node);
228 --CurrentNestingLevel;
229 return ShouldContinue;
232 bool traverseDeclWithIncreasedNestingLevel(
Decl *Node) {
233 ++CurrentNestingLevel;
234 bool ShouldContinue = Base::TraverseDecl(Node);
235 --CurrentNestingLevel;
236 return ShouldContinue;
239 bool TraverseIfStmt(IfStmt *Node,
bool InElseIf =
false) {
241 return Base::TraverseIfStmt(Node);
244 CognitiveComplexity::Criteria Reasons =
245 CognitiveComplexity::Criteria::None;
248 Reasons |= CognitiveComplexity::Criteria::Increment;
250 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
255 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
258 CC.account(
Node->getIfLoc(), CurrentNestingLevel, Reasons);
267 if (!TraverseStmt(
Node->getInit()))
270 if (!TraverseStmt(
Node->getCond()))
273 if (!traverseStmtWithIncreasedNestingLevel(
Node->getInit()))
276 if (!traverseStmtWithIncreasedNestingLevel(
Node->getCond()))
281 if (!traverseStmtWithIncreasedNestingLevel(
Node->getThen()))
284 if (!
Node->getElse())
287 if (
auto *
E = dyn_cast<IfStmt>(
Node->getElse()))
288 return TraverseIfStmt(
E,
true);
291 CognitiveComplexity::Criteria Reasons =
292 CognitiveComplexity::Criteria::None;
295 Reasons |= CognitiveComplexity::Criteria::Increment;
297 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
301 CC.account(
Node->getElseLoc(), CurrentNestingLevel, Reasons);
305 return traverseStmtWithIncreasedNestingLevel(
Node->getElse());
309#define CurrentBinaryOperator BinaryOperatorsStack.top()
313 bool TraverseBinaryOperator(BinaryOperator *Op) {
314 if (!Op || !Op->isLogicalOp())
315 return Base::TraverseBinaryOperator(Op);
318 if (BinaryOperatorsStack.empty())
319 BinaryOperatorsStack.emplace();
324 CC.account(Op->getOperatorLoc(), CurrentNestingLevel,
325 CognitiveComplexity::Criteria::Increment);
329 const std::optional<BinaryOperator::Opcode> BinOpCopy(
334 bool ShouldContinue = Base::TraverseBinaryOperator(Op);
339 return ShouldContinue;
344 bool TraverseCallExpr(CallExpr *Node) {
348 return Base::TraverseCallExpr(Node);
351 BinaryOperatorsStack.emplace();
352 bool ShouldContinue = Base::TraverseCallExpr(Node);
354 BinaryOperatorsStack.pop();
356 return ShouldContinue;
359#undef CurrentBinaryOperator
361 bool TraverseStmt(Stmt *Node) {
363 return Base::TraverseStmt(Node);
365 if (IgnoreMacros &&
Node->getBeginLoc().isMacroID())
371 CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None;
376 switch (
Node->getStmtClass()) {
379 case Stmt::ConditionalOperatorClass:
380 case Stmt::SwitchStmtClass:
381 case Stmt::ForStmtClass:
382 case Stmt::CXXForRangeStmtClass:
383 case Stmt::WhileStmtClass:
384 case Stmt::DoStmtClass:
385 case Stmt::CXXCatchStmtClass:
386 case Stmt::GotoStmtClass:
387 case Stmt::IndirectGotoStmtClass:
388 Reasons |= CognitiveComplexity::Criteria::Increment;
399 switch (
Node->getStmtClass()) {
402 case Stmt::ConditionalOperatorClass:
403 case Stmt::SwitchStmtClass:
404 case Stmt::ForStmtClass:
405 case Stmt::CXXForRangeStmtClass:
406 case Stmt::WhileStmtClass:
407 case Stmt::DoStmtClass:
408 case Stmt::CXXCatchStmtClass:
409 case Stmt::LambdaExprClass:
410 case Stmt::StmtExprClass:
411 Reasons |= CognitiveComplexity::Criteria::IncrementNesting;
420 switch (
Node->getStmtClass()) {
422 case Stmt::ConditionalOperatorClass:
423 case Stmt::SwitchStmtClass:
424 case Stmt::ForStmtClass:
425 case Stmt::CXXForRangeStmtClass:
426 case Stmt::WhileStmtClass:
427 case Stmt::DoStmtClass:
428 case Stmt::CXXCatchStmtClass:
429 Reasons |= CognitiveComplexity::Criteria::PenalizeNesting;
435 if (
Node->getStmtClass() == Stmt::ConditionalOperatorClass) {
439 Location = cast<ConditionalOperator>(Node)->getQuestionLoc();
443 if (Reasons & CognitiveComplexity::Criteria::All)
444 CC.account(
Location, CurrentNestingLevel, Reasons);
447 if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting))
448 return Base::TraverseStmt(Node);
450 return traverseStmtWithIncreasedNestingLevel(Node);
462 bool TraverseDecl(
Decl *Node,
bool MainAnalyzedFunction =
false) {
463 if (!Node || MainAnalyzedFunction)
464 return Base::TraverseDecl(Node);
468 switch (
Node->getKind()) {
470 case Decl::CXXMethod:
471 case Decl::CXXConstructor:
472 case Decl::CXXDestructor:
477 return Base::TraverseDecl(Node);
481 CC.account(
Node->getBeginLoc(), CurrentNestingLevel,
482 CognitiveComplexity::Criteria::IncrementNesting);
484 return traverseDeclWithIncreasedNestingLevel(Node);
487 CognitiveComplexity
CC;
495 Threshold(Options.get(
"Threshold", CognitiveComplexity::
DefaultLimit)),
496 DescribeBasicIncrements(Options.get(
"DescribeBasicIncrements", true)),
497 IgnoreMacros(Options.get(
"IgnoreMacros", false)) {}
502 Options.
store(Opts,
"DescribeBasicIncrements", DescribeBasicIncrements);
508 functionDecl(isDefinition(),
509 unless(anyOf(isDefaulted(), isDeleted(), isWeak())))
512 Finder->addMatcher(lambdaExpr().bind(
"lambda"),
this);
516 const MatchFinder::MatchResult &Result) {
518 FunctionASTVisitor Visitor(IgnoreMacros);
521 const auto *
TheDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"func");
522 const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>(
"lambda");
525 "The matchers should only match the functions that "
526 "have user-provided body.");
528 Visitor.TraverseDecl(
const_cast<FunctionDecl *
>(
TheDecl),
true);
530 Loc = TheLambdaExpr->getBeginLoc();
531 Visitor.TraverseLambdaExpr(
const_cast<LambdaExpr *
>(TheLambdaExpr));
534 if (Visitor.CC.Total <= Threshold)
538 diag(
Loc,
"function %0 has cognitive complexity of %1 (threshold %2)")
539 <<
TheDecl << Visitor.CC.Total << Threshold;
541 diag(
Loc,
"lambda has cognitive complexity of %0 (threshold %1)")
542 << Visitor.CC.Total << Threshold;
544 if (!DescribeBasicIncrements)
548 for (
const auto &Detail : Visitor.CC.Details) {
550 unsigned short Increase = 0;
551 std::tie(MsgId, Increase) = Detail.process();
552 assert(MsgId < Msgs.size() &&
"MsgId should always be valid");
555 diag(Detail.Loc, Msgs[MsgId], DiagnosticIDs::Note)
556 << (unsigned)Increase << (
unsigned)Detail.Nesting << 1 + Detail.Nesting;
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
#define CurrentBinaryOperator
static constexpr unsigned DefaultLimit
const unsigned short Nesting
SmallVector< Detail, DefaultLimit > Details
::clang::DynTypedNode Node
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