1 //===--- USRFinder.cpp - Clang refactoring library ------------------------===//
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 /// \file Implements a recursive AST visitor that finds the USR of a symbol at a
10 /// point.
11 ///
12 //===----------------------------------------------------------------------===//
13 
14 #include "clang/Tooling/Refactoring/Rename/USRFinder.h"
15 #include "clang/AST/AST.h"
16 #include "clang/AST/ASTContext.h"
17 #include "clang/AST/RecursiveASTVisitor.h"
18 #include "clang/Index/USRGeneration.h"
19 #include "clang/Lex/Lexer.h"
20 #include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
21 #include "llvm/ADT/SmallVector.h"
22 
23 using namespace llvm;
24 
25 namespace clang {
26 namespace tooling {
27 
28 namespace {
29 
30 /// Recursively visits each AST node to find the symbol underneath the cursor.
31 class NamedDeclOccurrenceFindingVisitor
32     : public RecursiveSymbolVisitor<NamedDeclOccurrenceFindingVisitor> {
33 public:
34   // Finds the NamedDecl at a point in the source.
35   // \param Point the location in the source to search for the NamedDecl.
36   explicit NamedDeclOccurrenceFindingVisitor(const SourceLocation Point,
37                                              const ASTContext &Context)
38       : RecursiveSymbolVisitor(Context.getSourceManager(),
39                                Context.getLangOpts()),
40         Point(Point), Context(Context) {}
41 
42   bool visitSymbolOccurrence(const NamedDecl *ND,
43                              ArrayRef<SourceRange> NameRanges) {
44     if (!ND)
45       return true;
46     for (const auto &Range : NameRanges) {
47       SourceLocation Start = Range.getBegin();
48       SourceLocation End = Range.getEnd();
49       if (!Start.isValid() || !Start.isFileID() || !End.isValid() ||
50           !End.isFileID() || !isPointWithin(Start, End))
51         return true;
52     }
53     Result = ND;
54     return false;
55   }
56 
57   const NamedDecl *getNamedDecl() const { return Result; }
58 
59 private:
60   // Determines if the Point is within Start and End.
61   bool isPointWithin(const SourceLocation Start, const SourceLocation End) {
62     // FIXME: Add tests for Point == End.
63     return Point == Start || Point == End ||
64            (Context.getSourceManager().isBeforeInTranslationUnit(Start,
65                                                                  Point) &&
66             Context.getSourceManager().isBeforeInTranslationUnit(Point, End));
67   }
68 
69   const NamedDecl *Result = nullptr;
70   const SourceLocation Point; // The location to find the NamedDecl.
71   const ASTContext &Context;
72 };
73 
74 } // end anonymous namespace
75 
76 const NamedDecl *getNamedDeclAt(const ASTContext &Context,
77                                 const SourceLocation Point) {
78   const SourceManager &SM = Context.getSourceManager();
79   NamedDeclOccurrenceFindingVisitor Visitor(Point, Context);
80 
81   // Try to be clever about pruning down the number of top-level declarations we
82   // see. If both start and end is either before or after the point we're
83   // looking for the point cannot be inside of this decl. Don't even look at it.
84   for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) {
85     SourceLocation StartLoc = CurrDecl->getBeginLoc();
86     SourceLocation EndLoc = CurrDecl->getEndLoc();
87     if (StartLoc.isValid() && EndLoc.isValid() &&
88         SM.isBeforeInTranslationUnit(StartLoc, Point) !=
89             SM.isBeforeInTranslationUnit(EndLoc, Point))
90       Visitor.TraverseDecl(CurrDecl);
91   }
92 
93   return Visitor.getNamedDecl();
94 }
95 
96 namespace {
97 
98 /// Recursively visits each NamedDecl node to find the declaration with a
99 /// specific name.
100 class NamedDeclFindingVisitor
101     : public RecursiveASTVisitor<NamedDeclFindingVisitor> {
102 public:
103   explicit NamedDeclFindingVisitor(StringRef Name) : Name(Name) {}
104 
105   // We don't have to traverse the uses to find some declaration with a
106   // specific name, so just visit the named declarations.
107   bool VisitNamedDecl(const NamedDecl *ND) {
108     if (!ND)
109       return true;
110     // Fully qualified name is used to find the declaration.
111     if (Name != ND->getQualifiedNameAsString() &&
112         Name != "::" + ND->getQualifiedNameAsString())
113       return true;
114     Result = ND;
115     return false;
116   }
117 
118   const NamedDecl *getNamedDecl() const { return Result; }
119 
120 private:
121   const NamedDecl *Result = nullptr;
122   StringRef Name;
123 };
124 
125 } // end anonymous namespace
126 
127 const NamedDecl *getNamedDeclFor(const ASTContext &Context,
128                                  const std::string &Name) {
129   NamedDeclFindingVisitor Visitor(Name);
130   Visitor.TraverseDecl(Context.getTranslationUnitDecl());
131   return Visitor.getNamedDecl();
132 }
133 
134 std::string getUSRForDecl(const Decl *Decl) {
135   llvm::SmallVector<char, 128> Buff;
136 
137   // FIXME: Add test for the nullptr case.
138   if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff))
139     return "";
140 
141   return std::string(Buff.data(), Buff.size());
142 }
143 
144 } // end namespace tooling
145 } // end namespace clang
146