clang-tools 24.0.0git
StaticInitializationCycleCheck.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
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/DynamicRecursiveASTVisitor.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Analysis/CallGraph.h"
14#include "llvm/ADT/DenseMap.h"
15#include "llvm/ADT/SCCIterator.h"
16
17using namespace clang;
18using namespace clang::ast_matchers;
19
20// Check if a reference to a static variable (that was reached while traversal
21// of a function declaration) should be ignored by the check. This returns true
22// if the value of the variable has no effect on the return value of the
23// function, or the reference is ignored for other reason to eliminate FP
24// results.
25// Ignore happens if the variable appears at LHS of an assignment or it appears
26// inside a compile-time constant expression (like 'sizeof').
27// Additional condition is if the reference appears in a not immediately called
28// lambda function.
29static bool shouldIgnoreRef(const DeclRefExpr *DRE, const Decl *ParentD) {
30 ASTContext &ACtx = ParentD->getASTContext();
31 ParentMapContext &PMC = ACtx.getParentMapContext();
32 DynTypedNodeList Parents = PMC.getParents(*DRE);
33 // While going upwards on the parent graph, this stores the last encountered
34 // lambda expression that did not appear (until now) as a callee of a
35 // 'operator ()'.
36 const LambdaExpr *ParentLambda = nullptr;
37 while (!Parents.empty()) {
38 if (Parents.size() > 1)
39 return true;
40 if (const Expr *E = Parents[0].get<Expr>()) {
41 if (!E->getType().isNull() && !E->isValueDependent() &&
42 E->isIntegerConstantExpr(ACtx))
43 return true;
44 if (const auto *ParentBO = dyn_cast<BinaryOperator>(E)) {
45 if (ParentBO->isAssignmentOp() &&
46 ParentBO->getLHS()->IgnoreParenCasts() == DRE)
47 return true;
48 } else if (const auto *LambdaE = dyn_cast<LambdaExpr>(E)) {
49 // Found another lambda while the last found do not appear to be called
50 // by '()'.
51 if (ParentLambda)
52 return true;
53 ParentLambda = LambdaE;
54 } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(E)) {
55 // Check if the last found lambda is called with this 'operator ()'.
56 if (ParentLambda &&
57 OpCallE->getOperator() == OverloadedOperatorKind::OO_Call &&
58 OpCallE->getCalleeDecl() == ParentLambda->getCallOperator())
59 ParentLambda = nullptr;
60 }
61 } else if (const Decl *D = Parents[0].get<Decl>()) {
62 // Check if we reached the root of the context (variable or function
63 // declaration) to check.
64 if ([D, ParentD]() {
65 if (const auto *ParentF = dyn_cast<FunctionDecl>(ParentD)) {
66 if (const auto *FD = dyn_cast<FunctionDecl>(D))
67 return FD == ParentF->getDefinition();
68 return false;
69 }
70 return D->getCanonicalDecl() == ParentD->getCanonicalDecl();
71 }())
72 return ParentLambda != nullptr;
73 }
74 Parents = PMC.getParents(Parents[0]);
75 }
76 llvm_unreachable("declaration of ParentD should be reached");
77 return false;
78}
79
80namespace {
81
82class VarUseNode;
83
84// Store the reference to a variable or the call location of a function.
85// 'Ref' is a DeclRefExpr or a CallExpr.
86// 'Node' contains information about corresponding VarDecl or FunctionDecl.
87struct VarUseRecord {
88 const Expr *Ref;
89 VarUseNode *Node;
90
91 VarUseRecord() = default;
92 VarUseRecord(const Expr *Ref, VarUseNode *N) : Ref(Ref), Node(N) {}
93 operator VarUseNode *() const { return Node; }
94};
95
96// One node in the variable usage graph.
97// If 'D' is a VarDecl:
98// 'Uses' contains all static variables and global function calls in the
99// initializer expression.
100// If 'D' is a FunctionDecl:
101// 'Uses' contains all static variable references and global function calls in
102// the function body.
103class VarUseNode {
104 const NamedDecl *D;
105 llvm::SmallVector<VarUseRecord, 2> Uses;
106
107public:
108 VarUseNode(const NamedDecl *D) : D(D) {}
109
110 const NamedDecl *getDecl() const { return D; }
111 bool isVar() const { return isa<VarDecl>(D); }
112 bool isFunction() const { return isa<FunctionDecl>(D); }
113 const VarDecl *getVar() const { return cast<VarDecl>(D); }
114 const FunctionDecl *getFunction() const { return cast<FunctionDecl>(D); }
115
116 using const_iterator = llvm::SmallVectorImpl<VarUseRecord>::const_iterator;
117
118 const_iterator begin() const { return Uses.begin(); }
119 const_iterator end() const { return Uses.end(); }
120
121 llvm::iterator_range<const_iterator> uses() const {
122 return llvm::make_range(begin(), end());
123 }
124
125 bool empty() const { return Uses.empty(); }
126 unsigned size() const { return Uses.size(); }
127
128 friend class VarUseCollector;
129 friend class VarUseGraphBuilder;
130 friend class VarUseGraph;
131};
132
133// "Variable usage graph":
134// Stores dependencies of variables from other variables or function calls,
135// and dependencies of function results from variables or functions.
136// Only static variables (static member, static local variable, or global
137// variable) and global or static functions are stored.
138// Stored are the canonical declarations of variables and definitions of
139// functions.
140class VarUseGraph {
141 using UseMapTy = llvm::DenseMap<const Decl *, std::unique_ptr<VarUseNode>>;
142
143 UseMapTy UseMap;
144
145public:
146 VarUseGraph() {
147 // A special "root" is added at nullptr location.
148 // It contains edges to all other nodes, without a "Ref" expression.
149 // This is used by the SCC algorithm.
150 UseMap[nullptr] = std::make_unique<VarUseNode>(nullptr);
151 }
152
153 VarUseNode *addNode(const NamedDecl *D) {
154 std::unique_ptr<VarUseNode> &N = UseMap[D];
155 if (N)
156 return N.get();
157 N = std::make_unique<VarUseNode>(D);
158 UseMap[nullptr]->Uses.emplace_back(nullptr, N.get());
159 return N.get();
160 }
161
162 using const_iterator = UseMapTy::const_iterator;
163
164 const_iterator begin() const { return UseMap.begin(); }
165 const_iterator end() const { return UseMap.end(); }
166
167 unsigned size() const { return UseMap.size(); }
168
169 VarUseNode *getRoot() { return UseMap[nullptr].get(); }
170
171 friend class VarUseGraphBuilder;
172};
173
174// Collect static variable references and static function calls.
175// This is used with initializer expressions and function body statements.
176// At initializer expressions only statements (and expressions) should be
177// traversed. But for functions declarations are needed too (to reach
178// initializations of variables) (only inside the given function).
179class VarUseCollector : public DynamicRecursiveASTVisitor {
180 VarUseNode *Node;
181 VarUseGraph &G;
182 const DeclContext *DC;
183
184public:
185 VarUseCollector(VarUseNode *N, VarUseGraph &G)
186 : Node(N), G(G), DC(N->isFunction() ? N->getFunction() : nullptr) {}
187
188 bool TraverseType(QualType T, bool TraverseQualifier) override {
189 return true;
190 }
191 bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier) override {
192 return true;
193 }
194 bool TraverseAttr(Attr *At) override { return true; }
195 bool TraverseDecl(Decl *D) override {
196 if (D && DC && DC->containsDecl(D))
197 return DynamicRecursiveASTVisitor::TraverseDecl(D);
198 return true;
199 }
200
201 bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
202 if (const auto *VarD = dyn_cast<VarDecl>(DRE->getDecl());
203 VarD && (!shouldIgnoreRef(DRE, Node->getDecl()) &&
204 (VarD->hasGlobalStorage() || VarD->isStaticLocal())))
205 Node->Uses.emplace_back(DRE, G.addNode(VarD->getCanonicalDecl()));
206 return true;
207 }
208
209 bool VisitCallExpr(CallExpr *CE) override {
210 if (const FunctionDecl *F = CE->getDirectCallee();
211 F && (F->isGlobal() || F->isStatic())) {
212 const FunctionDecl *Def = F->getDefinition();
213 if (Def)
214 Node->Uses.emplace_back(CE, G.addNode(Def));
215 }
216 return true;
217 }
218};
219
220// Build the complete graph by visiting all static variables and functions and
221// add all "usages" (children in the graph) to it.
222// Every variable and function is visited once (at canonical declaration or the
223// definition). When visiting an object, a node for it may already exist
224// (without added children) if a reference to it was found already.
225class VarUseGraphBuilder : public DynamicRecursiveASTVisitor {
226 VarUseGraph &G;
227
228public:
229 VarUseGraphBuilder(VarUseGraph &G) : G(G) {}
230
231 bool VisitVarDecl(VarDecl *VD) override {
232 if ((VD->hasGlobalStorage() || VD->isStaticLocal()) &&
233 VD->isCanonicalDecl()) {
234 if (VarDecl *InitD = VD->getInitializingDeclaration()) {
235 VarUseNode *N = G.addNode(VD);
236 VarUseCollector Collector(N, G);
237 Collector.TraverseStmt(InitD->getInit());
238 }
239 }
240 return true;
241 }
242
243 bool VisitFunctionDecl(FunctionDecl *FD) override {
244 if (FD->isGlobal() || FD->isStatic()) {
245 if (Stmt *Body = FD->getBody()) {
246 VarUseNode *N = G.addNode(FD);
247 VarUseCollector Collector(N, G);
248 Collector.TraverseStmt(Body);
249 }
250 }
251 return true;
252 }
253};
254
255} // namespace
256
257namespace llvm {
258
259// These structures are required by scc_iterator.
260
261template <> struct GraphTraits<const VarUseNode *> {
262 using NodeType = const VarUseNode;
263 using NodeRef = const VarUseNode *;
264 using ChildIteratorType = NodeType::const_iterator;
265
266 static NodeType *getEntryNode(const VarUseNode *N) { return N; }
267 static ChildIteratorType
268 child_begin(NodeType *N) { // NOLINT(readability-identifier-naming)
269 return N->begin();
270 }
271 static ChildIteratorType
272 child_end(NodeType *N) { // NOLINT(readability-identifier-naming)
273 return N->end();
274 }
275};
276
277template <>
278struct GraphTraits<const VarUseGraph *>
279 : public GraphTraits<const VarUseNode *> {
280 static NodeType *getEntryNode(const VarUseGraph *G) {
281 return const_cast<VarUseGraph *>(G)->getRoot();
282 }
283
284 static VarUseNode *getValue(VarUseGraph::const_iterator::value_type &P) {
285 return P.second.get();
286 }
287
289 mapped_iterator<VarUseGraph::const_iterator, decltype(&getValue)>;
290
291 static nodes_iterator
292 nodes_begin(const VarUseGraph *G) { // NOLINT(readability-identifier-naming)
293 return {G->begin(), &getValue};
294 }
295
296 static nodes_iterator
297 nodes_end(const VarUseGraph *G) { // NOLINT(readability-identifier-naming)
298 return {G->end(), &getValue};
299 }
300
301 static unsigned size(const VarUseGraph *G) { return G->size(); }
302};
303
304} // namespace llvm
305
306static void
307reportCycles(ArrayRef<const VarUseNode *> SCC,
309 // Check if the SCC contains any variable, otherwise it is a function
310 // recursion.
311 const auto NodeIsVar = [](const VarUseNode *N) { return N->isVar(); };
312 const auto *VarNode = llvm::find_if(SCC, NodeIsVar);
313 if (VarNode == SCC.end())
314 return;
315
316 Chk.diag((*VarNode)->getDecl()->getLocation(),
317 "static variable initialization cycle detected involving %0")
318 << (*VarNode)->getDecl();
319
320 // SCC may contain multiple cycles.
321 // Find one path with the front node as start.
322
323 // Lookup if a node is part of current SCC.
324 const llvm::SmallPtrSet<const VarUseNode *, 4> SCCElts(SCC.begin(),
325 SCC.end());
326
327 // Visit all paths in the SCC until we reach the front again.
328 llvm::DenseMap<const VarUseNode *, VarUseNode::const_iterator> NextNode;
329 llvm::SmallVector<const VarUseNode *> FoundPath;
330 FoundPath.push_back(SCC.front());
331 while (!FoundPath.empty()) {
332 if (!NextNode.contains(FoundPath.back())) {
333 NextNode[FoundPath.back()] = FoundPath.back()->begin();
334 } else {
335 NextNode[FoundPath.back()]++;
336 if (NextNode[FoundPath.back()] == FoundPath.back()->end()) {
337 FoundPath.pop_back();
338 continue;
339 }
340 }
341 const VarUseNode *N = (*NextNode[FoundPath.back()]).Node;
342 if (N == SCC.front())
343 break;
344 if (!SCCElts.contains(N) || NextNode.contains(N))
345 continue;
346 FoundPath.push_back(N);
347 }
348
349 std::string OutStr;
350 llvm::raw_string_ostream CycleOs(OutStr);
351
352 for (const VarUseNode *N : FoundPath) {
353 const VarUseRecord &U = *NextNode[N];
354 // 'U' is the source of the value, 'N->getDecl()' is the destination
355 Chk.diag(U.Ref->getBeginLoc(),
356 "%select{result|value}2 of %0 may be used to %select{compute "
357 "result of|initialize variable}3 %1 here",
358 DiagnosticIDs::Note)
359 << U.Node->getDecl() << N->getDecl() << U.Node->isVar() << N->isVar();
360
361 CycleOs << *N->getDecl() << " -> ";
362 }
363 CycleOs << *FoundPath.front()->getDecl();
364
365 Chk.diag((*VarNode)->getDecl()->getLocation(),
366 "possible cyclical initialization: %0", DiagnosticIDs::Note)
367 << CycleOs.str();
368}
369
370namespace clang::tidy::misc {
371
373 Finder->addMatcher(translationUnitDecl().bind("TUDecl"), this);
374}
375
377 const MatchFinder::MatchResult &Result) {
378 const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("TUDecl");
379
380 VarUseGraph Uses;
381 VarUseGraphBuilder Builder(Uses);
382 Builder.TraverseDecl(const_cast<TranslationUnitDecl *>(TU));
383
384 for (llvm::scc_iterator<const VarUseGraph *> SCCI =
385 llvm::scc_begin(const_cast<const VarUseGraph *>(&Uses));
386 !SCCI.isAtEnd(); ++SCCI) {
387 if (!SCCI.hasCycle())
388 continue;
389 reportCycles(*SCCI, *this);
390 }
391}
392
393} // namespace clang::tidy::misc
static void reportCycles(ArrayRef< const VarUseNode * > SCC, clang::tidy::misc::StaticInitializationCycleCheck &Chk)
static bool shouldIgnoreRef(const DeclRefExpr *DRE, const Decl *ParentD)
Finds cyclical initialization of static variables.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition Generators.h:150
static VarUseNode * getValue(VarUseGraph::const_iterator::value_type &P)
static nodes_iterator nodes_end(const VarUseGraph *G)
static NodeType * getEntryNode(const VarUseGraph *G)
mapped_iterator< VarUseGraph::const_iterator, decltype(&getValue)> nodes_iterator
static nodes_iterator nodes_begin(const VarUseGraph *G)
static NodeType * getEntryNode(const VarUseNode *N)
static ChildIteratorType child_begin(NodeType *N)