1 | //===-- HelperDeclRefGraph.cpp - AST-based call graph for helper decls ----===// |
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 "HelperDeclRefGraph.h" |
10 | #include "Move.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "llvm/Support/Debug.h" |
13 | #include <vector> |
14 | |
15 | #define DEBUG_TYPE "clang-move" |
16 | |
17 | namespace clang { |
18 | namespace move { |
19 | |
20 | void HelperDeclRefGraph::print(raw_ostream &OS) const { |
21 | OS << " --- Call graph Dump --- \n" ; |
22 | for (auto I = DeclMap.begin(); I != DeclMap.end(); ++I) { |
23 | const CallGraphNode *N = (I->second).get(); |
24 | |
25 | OS << " Declarations: " ; |
26 | N->print(os&: OS); |
27 | OS << " (" << N << ") " ; |
28 | OS << " calls: " ; |
29 | for (auto CI = N->begin(), CE = N->end(); CI != CE; ++CI) { |
30 | CI->Callee->print(os&: OS); |
31 | OS << " (" << CI << ") " ; |
32 | } |
33 | OS << '\n'; |
34 | } |
35 | OS.flush(); |
36 | } |
37 | |
38 | void HelperDeclRefGraph::addEdge(const Decl *Caller, const Decl *Callee) { |
39 | assert(Caller); |
40 | assert(Callee); |
41 | |
42 | // Ignore the case where Caller equals Callee. This happens in the static |
43 | // class member definitions in global namespace like "int CLASS::static_var = |
44 | // 1;", its DC is a VarDel whose outmost enclosing declaration is the "CLASS" |
45 | // CXXRecordDecl. |
46 | if (Caller == Callee) return; |
47 | |
48 | // Allocate a new node, mark it as root, and process it's calls. |
49 | CallGraphNode *CallerNode = getOrInsertNode(D: const_cast<Decl *>(Caller)); |
50 | CallGraphNode *CalleeNode = getOrInsertNode(D: const_cast<Decl *>(Callee)); |
51 | CallerNode->addCallee(Call: {CalleeNode, /*CallExpr=*/nullptr}); |
52 | } |
53 | |
54 | void HelperDeclRefGraph::dump() const { print(OS&: llvm::errs()); } |
55 | |
56 | CallGraphNode *HelperDeclRefGraph::getOrInsertNode(Decl *F) { |
57 | F = F->getCanonicalDecl(); |
58 | std::unique_ptr<CallGraphNode> &Node = DeclMap[F]; |
59 | if (Node) |
60 | return Node.get(); |
61 | |
62 | Node = std::make_unique<CallGraphNode>(args&: F); |
63 | return Node.get(); |
64 | } |
65 | |
66 | CallGraphNode *HelperDeclRefGraph::getNode(const Decl *D) const { |
67 | auto I = DeclMap.find(Val: D->getCanonicalDecl()); |
68 | return I == DeclMap.end() ? nullptr : I->second.get(); |
69 | } |
70 | |
71 | llvm::DenseSet<const CallGraphNode *> |
72 | HelperDeclRefGraph::getReachableNodes(const Decl *Root) const { |
73 | const auto *RootNode = getNode(D: Root); |
74 | if (!RootNode) |
75 | return {}; |
76 | llvm::DenseSet<const CallGraphNode *> ConnectedNodes; |
77 | std::function<void(const CallGraphNode *)> VisitNode = |
78 | [&](const CallGraphNode *Node) { |
79 | if (ConnectedNodes.count(V: Node)) |
80 | return; |
81 | ConnectedNodes.insert(V: Node); |
82 | for (auto It = Node->begin(), End = Node->end(); It != End; ++It) |
83 | VisitNode(*It); |
84 | }; |
85 | |
86 | VisitNode(RootNode); |
87 | return ConnectedNodes; |
88 | } |
89 | |
90 | const Decl *HelperDeclRGBuilder::getOutmostClassOrFunDecl(const Decl *D) { |
91 | const auto *DC = D->getDeclContext(); |
92 | const auto *Result = D; |
93 | while (DC) { |
94 | if (const auto *RD = dyn_cast<CXXRecordDecl>(Val: DC)) |
95 | Result = RD; |
96 | else if (const auto *FD = dyn_cast<FunctionDecl>(Val: DC)) |
97 | Result = FD; |
98 | DC = DC->getParent(); |
99 | } |
100 | return Result; |
101 | } |
102 | |
103 | void HelperDeclRGBuilder::run( |
104 | const ast_matchers::MatchFinder::MatchResult &Result) { |
105 | // Construct the graph by adding a directed edge from caller to callee. |
106 | // |
107 | // "dc" is the closest ancestor declaration of "func_ref" or "used_class", it |
108 | // might be not the targetted Caller Decl, we always use the outmost enclosing |
109 | // FunctionDecl/CXXRecordDecl of "dc". For example, |
110 | // |
111 | // int MoveClass::F() { int a = helper(); return a; } |
112 | // |
113 | // The matched "dc" of "helper" DeclRefExpr is a VarDecl, we traverse up AST |
114 | // to find the outmost "MoveClass" CXXRecordDecl and use it as Caller. |
115 | if (const auto *FuncRef = Result.Nodes.getNodeAs<DeclRefExpr>(ID: "func_ref" )) { |
116 | const auto *DC = Result.Nodes.getNodeAs<Decl>(ID: "dc" ); |
117 | assert(DC); |
118 | LLVM_DEBUG(llvm::dbgs() << "Find helper function usage: " |
119 | << FuncRef->getDecl()->getDeclName() << " (" |
120 | << FuncRef->getDecl() << ")\n" ); |
121 | RG->addEdge( |
122 | Caller: getOutmostClassOrFunDecl(D: DC->getCanonicalDecl()), |
123 | Callee: getOutmostClassOrFunDecl(D: FuncRef->getDecl()->getCanonicalDecl())); |
124 | } else if (const auto *UsedClass = |
125 | Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "used_class" )) { |
126 | const auto *DC = Result.Nodes.getNodeAs<Decl>(ID: "dc" ); |
127 | assert(DC); |
128 | LLVM_DEBUG(llvm::dbgs() |
129 | << "Find helper class usage: " << UsedClass->getDeclName() |
130 | << " (" << UsedClass << ")\n" ); |
131 | RG->addEdge(getOutmostClassOrFunDecl(D: DC->getCanonicalDecl()), UsedClass); |
132 | } |
133 | } |
134 | |
135 | } // namespace move |
136 | } // namespace clang |
137 | |