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