1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "clangreferencescollector.h"
27 
28 #include "clangstring.h"
29 #include "cursor.h"
30 #include "sourcerange.h"
31 #include "token.h"
32 
33 #include <clangsupport/sourcerangecontainer.h>
34 #include <utils/qtcassert.h>
35 
36 #include <utf8string.h>
37 
38 #include <QDebug>
39 
40 namespace ClangBackEnd {
41 
42 namespace {
43 
44 class ReferencedCursor
45 {
46 public:
find(const Cursor & cursor)47     static ReferencedCursor find(const Cursor &cursor)
48     {
49         // Query the referenced cursor directly instead of first testing with cursor.isReference().
50         // cursor.isReference() reports false for e.g. CXCursor_DeclRefExpr or CXCursor_CallExpr
51         // although it returns a valid cursor.
52         const Cursor referenced = cursor.referenced();
53         if (referenced.isValid())
54             return handleReferenced(referenced);
55 
56         const Cursor definition = cursor.definition();
57         if (definition.isValid())
58             return definition;
59 
60         return cursor;
61     }
62 
usr() const63     Utf8String usr() const
64     {
65         return cursor.unifiedSymbolResolution() + usrSuffix;
66     }
67 
isLocalVariable() const68     bool isLocalVariable() const
69     {
70         return cursor.isLocalVariable();
71     }
72 
73 private:
ReferencedCursor(const Cursor & cursor,const Utf8String & usrSuffix=Utf8String ())74     ReferencedCursor(const Cursor &cursor, const Utf8String &usrSuffix = Utf8String())
75         : cursor(cursor)
76         , usrSuffix(usrSuffix)
77     {}
78 
handleReferenced(const Cursor & cursor)79     static ReferencedCursor handleReferenced(const Cursor &cursor)
80     {
81         if (cursor.kind() == CXCursor_OverloadedDeclRef) {
82             // e.g. Text cursor is on "App" of "using N::App;".
83             if (cursor.overloadedDeclarationsCount() >= 1)
84                 return cursor.overloadedDeclaration(0);
85         }
86 
87         if (cursor.isConstructorOrDestructor()) {
88             const Type type = cursor.type();
89             if (type.isValid()) {
90                 const Cursor typeDeclaration = type.declaration();
91                 if (typeDeclaration.isValid()) {
92                     // A CXCursor_CallExpr like "new Foo" has a type of CXType_Record and its
93                     // declaration is e.g. CXCursor_ClassDecl.
94                     return typeDeclaration;
95                 } else {
96                     // A CXCursor_Constructor like "Foo();" has a type of CXType_FunctionProto
97                     // and its type declaration is invalid, so use the semantic parent.
98                     const Cursor parent = cursor.semanticParent();
99                     if (parent.isValid())
100                         return parent;
101                 }
102             }
103         }
104 
105         if (cursor.isFunctionLike() || cursor.isTemplateLike()) {
106             const Cursor parent = cursor.semanticParent();
107             const ClangString spelling = cursor.spelling();
108             return {parent, Utf8StringLiteral("_qtc_") + Utf8String(spelling)};
109         }
110 
111         return cursor;
112     }
113 
114 private:
115     Cursor cursor;
116     Utf8String usrSuffix;
117 };
118 
119 class ReferencesCollector
120 {
121 public:
122     ReferencesCollector(CXTranslationUnit cxTranslationUnit);
123 
124     ReferencesResult collect(uint line, uint column, bool localReferences = false) const;
125 
126 private:
127     bool pointsToIdentifier(int line, int column, unsigned *tokenIndex) const;
128     bool matchesIdentifier(const Token &token, const Utf8String &identifier) const;
129     bool checkToken(unsigned index, const Utf8String &identifier, const Utf8String &usr) const;
130 
131 private:
132     CXTranslationUnit m_cxTranslationUnit = nullptr;
133     Tokens m_tokens;
134     std::vector<Cursor> m_cursors;
135 };
136 
ReferencesCollector(CXTranslationUnit cxTranslationUnit)137 ReferencesCollector::ReferencesCollector(CXTranslationUnit cxTranslationUnit)
138     : m_cxTranslationUnit(cxTranslationUnit)
139     , m_tokens(Cursor(clang_getTranslationUnitCursor(m_cxTranslationUnit)).sourceRange())
140     , m_cursors(m_tokens.annotate())
141 {
142 }
143 
pointsToIdentifier(int line,int column,unsigned * tokenIndex) const144 bool ReferencesCollector::pointsToIdentifier(int line, int column, unsigned *tokenIndex) const
145 {
146     for (int i = 0; i < m_tokens.size(); ++i) {
147         const Token &token = m_tokens[i];
148         if (token.kind() == CXToken_Identifier && token.extent().contains(line, column)) {
149             *tokenIndex = i;
150             return true;
151         }
152     }
153 
154     return false;
155 }
156 
matchesIdentifier(const Token & token,const Utf8String & identifier) const157 bool ReferencesCollector::matchesIdentifier(const Token &token,
158                                             const Utf8String &identifier) const
159 {
160     const CXTokenKind tokenKind = token.kind();
161     if (tokenKind == CXToken_Identifier)
162         return token.spelling() == identifier;
163 
164     return false;
165 }
166 
checkToken(unsigned index,const Utf8String & identifier,const Utf8String & usr) const167 bool ReferencesCollector::checkToken(unsigned index, const Utf8String &identifier,
168                                      const Utf8String &usr) const
169 {
170     const Token &token = m_tokens[index];
171     if (!matchesIdentifier(token, identifier))
172         return false;
173 
174     { // For debugging only
175 //        const SourceRange range{m_cxTranslationUnit, clang_getTokenExtent(m_cxTranslationUnit, token)};
176 //        const uint line = range.start().line();
177 //        const ClangString spellingCs = clang_getTokenSpelling(m_cxTranslationUnit, token);
178 //        const Utf8String spelling = spellingCs;
179 //        qWarning() << "ReferencesCollector::checkToken:" << line << spelling;
180     }
181 
182     const ReferencedCursor candidate = ReferencedCursor::find(m_cursors[index]);
183 
184     return candidate.usr() == usr;
185 }
186 
collect(uint line,uint column,bool localReferences) const187 ReferencesResult ReferencesCollector::collect(uint line, uint column, bool localReferences) const
188 {
189     ReferencesResult result;
190 
191     unsigned index = 0;
192     if (!pointsToIdentifier(line, column, &index))
193         return result;
194 
195     const ReferencedCursor refCursor = ReferencedCursor::find(m_cursors[index]);
196     const Utf8String usr = refCursor.usr();
197     if (usr.isEmpty())
198         return result;
199 
200     if (localReferences && !refCursor.isLocalVariable())
201         return result;
202 
203     const Token &token = m_tokens[index];
204     const Utf8String identifier = token.spelling();
205     for (int i = 0; i < m_tokens.size(); ++i) {
206         if (checkToken(i, identifier, usr))
207             result.references.append(m_tokens[i].extent());
208     }
209 
210     result.isLocalVariable = refCursor.isLocalVariable();
211 
212     return result;
213 }
214 
215 } // anonymous namespace
216 
collectReferences(CXTranslationUnit cxTranslationUnit,uint line,uint column,bool localReferences)217 ReferencesResult collectReferences(CXTranslationUnit cxTranslationUnit,
218                                    uint line,
219                                    uint column,
220                                    bool localReferences)
221 {
222     ReferencesCollector collector(cxTranslationUnit);
223     return collector.collect(line, column, localReferences);
224 }
225 
226 } // namespace ClangBackEnd
227