clang-tools 23.0.0git
NoRecursionCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "NoRecursionCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Analysis/CallGraph.h"
13#include "llvm/ADT/SCCIterator.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::misc {
18
19static constexpr unsigned SmallCallStackSize = 16;
20static constexpr unsigned SmallSCCSize = 32;
21
23
24// In given SCC, find *some* call stack that will be cyclic.
25// This will only find *one* such stack, it might not be the smallest one,
26// and there may be other loops.
27static CallStackTy pathfindSomeCycle(ArrayRef<CallGraphNode *> SCC) {
28 // We'll need to be able to performantly look up whether some CallGraphNode
29 // is in SCC or not, so cache all the SCC elements in a set.
30 const llvm::SmallPtrSet<CallGraphNode *, SmallSCCSize> SCCElts(
31 llvm::from_range, SCC);
32
33 // Is node N part if the current SCC?
34 auto NodeIsPartOfSCC = [&SCCElts](CallGraphNode *N) {
35 return SCCElts.contains(N);
36 };
37
38 // Track the call stack that will cause a cycle.
39 llvm::SmallSetVector<CallGraphNode::CallRecord, SmallCallStackSize>
40 CallStackSet;
41
42 // Arbitrarily take the first element of SCC as entry point.
43 CallGraphNode::CallRecord EntryNode(SCC.front(), /*CallExpr=*/nullptr);
44 // Continue recursing into subsequent callees that are part of this SCC,
45 // and are thus known to be part of the call graph loop, until loop forms.
46 CallGraphNode::CallRecord *Node = &EntryNode;
47 while (true) {
48 // Did we see this node before?
49 if (!CallStackSet.insert(*Node))
50 break; // Cycle completed! Note that didn't insert the node into stack!
51 // Else, perform depth-first traversal: out of all callees, pick first one
52 // that is part of this SCC. This is not guaranteed to yield shortest cycle.
53 Node = llvm::find_if(Node->Callee->callees(), NodeIsPartOfSCC);
54 }
55
56 // Note that we failed to insert the last node, that completes the cycle.
57 // But we really want to have it. So insert it manually into stack only.
58 CallStackTy CallStack = CallStackSet.takeVector();
59 CallStack.emplace_back(*Node);
60
61 return CallStack;
62}
63
64void NoRecursionCheck::registerMatchers(MatchFinder *Finder) {
65 Finder->addMatcher(translationUnitDecl().bind("TUDecl"), this);
66}
67
68void NoRecursionCheck::handleSCC(ArrayRef<CallGraphNode *> SCC) {
69 assert(!SCC.empty() && "Empty SCC does not make sense.");
70
71 // First of all, call out every strongly connected function.
72 for (const CallGraphNode *N : SCC) {
73 const FunctionDecl *D = N->getDefinition();
74 diag(D->getLocation(), "function %0 is within a recursive call chain") << D;
75 }
76
77 // Now, SCC only tells us about strongly connected function declarations in
78 // the call graph. It doesn't *really* tell us about the cycles they form.
79 // And there may be more than one cycle in SCC.
80 // So let's form a call stack that eventually exposes *some* cycle.
81 const CallStackTy EventuallyCyclicCallStack = pathfindSomeCycle(SCC);
82 assert(!EventuallyCyclicCallStack.empty() && "We should've found the cycle");
83
84 // While last node of the call stack does cause a loop, due to the way we
85 // pathfind the cycle, the loop does not necessarily begin at the first node
86 // of the call stack, so drop front nodes of the call stack until it does.
87 const auto CyclicCallStack =
88 ArrayRef<CallGraphNode::CallRecord>(EventuallyCyclicCallStack)
89 .drop_until([LastNode = EventuallyCyclicCallStack.back()](
90 CallGraphNode::CallRecord FrontNode) {
91 return FrontNode == LastNode;
92 });
93 assert(CyclicCallStack.size() >= 2 && "Cycle requires at least 2 frames");
94
95 // Which function we decided to be the entry point that lead to the recursion?
96 const FunctionDecl *CycleEntryFn =
97 CyclicCallStack.front().Callee->getDefinition();
98 // And now, for ease of understanding, let's print the call sequence that
99 // forms the cycle in question.
100 diag(CycleEntryFn->getLocation(),
101 "example recursive call chain, starting from function %0",
102 DiagnosticIDs::Note)
103 << CycleEntryFn;
104 for (int CurFrame = 1, NumFrames = CyclicCallStack.size();
105 CurFrame != NumFrames; ++CurFrame) {
106 const CallGraphNode::CallRecord PrevNode = CyclicCallStack[CurFrame - 1];
107 const CallGraphNode::CallRecord CurrNode = CyclicCallStack[CurFrame];
108
109 Decl *PrevDecl = PrevNode.Callee->getDecl();
110 Decl *CurrDecl = CurrNode.Callee->getDecl();
111
112 diag(CurrNode.CallExpr->getBeginLoc(),
113 "Frame #%0: function %1 calls function %2 here:", DiagnosticIDs::Note)
114 << CurFrame << cast<NamedDecl>(PrevDecl) << cast<NamedDecl>(CurrDecl);
115 }
116
117 diag(CyclicCallStack.back().CallExpr->getBeginLoc(),
118 "... which was the starting point of the recursive call chain; there "
119 "may be other cycles",
120 DiagnosticIDs::Note);
121}
122
123void NoRecursionCheck::check(const MatchFinder::MatchResult &Result) {
124 // Build call graph for the entire translation unit.
125 const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("TUDecl");
126 CallGraph CG;
127 CG.addToCallGraph(const_cast<TranslationUnitDecl *>(TU));
128
129 // Look for cycles in call graph,
130 // by looking for Strongly Connected Components (SCC's)
131 for (llvm::scc_iterator<CallGraph *> SCCI = llvm::scc_begin(&CG),
132 SCCE = llvm::scc_end(&CG);
133 SCCI != SCCE; ++SCCI) {
134 if (!SCCI.hasCycle()) // We only care about cycles, not standalone nodes.
135 continue;
136 handleSCC(*SCCI);
137 }
138}
139
140} // namespace clang::tidy::misc
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static constexpr unsigned SmallSCCSize
SmallVector< CallGraphNode::CallRecord, SmallCallStackSize > CallStackTy
static constexpr unsigned SmallCallStackSize
static CallStackTy pathfindSomeCycle(ArrayRef< CallGraphNode * > SCC)