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 for Python.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28
29 #include "clangparser.h"
30 #include "clangutils.h"
31 #include "clangdebugutils.h"
32 #include "compilersupport.h"
33
34 #include <QtCore/QByteArrayList>
35 #include <QtCore/QDebug>
36 #include <QtCore/QDir>
37 #include <QtCore/QFile>
38 #include <QtCore/QScopedArrayPointer>
39 #include <QtCore/QString>
40
41 namespace clang {
42
getFileName(CXFile file)43 QString SourceFileCache::getFileName(CXFile file)
44 {
45 auto it = m_fileNameCache.find(file);
46 if (it == m_fileNameCache.end())
47 it = m_fileNameCache.insert(file, clang::getFileName(file));
48 return it.value();
49 }
50
getCodeSnippet(const CXCursor & cursor,QString * errorMessage)51 SourceFileCache::Snippet SourceFileCache::getCodeSnippet(const CXCursor &cursor,
52 QString *errorMessage)
53 {
54 Snippet result(nullptr, nullptr);
55
56 if (errorMessage)
57 errorMessage->clear();
58
59 const SourceRange range = getCursorRange(cursor);
60 // Quick check for equal locations: Frequently happens if the code is
61 // the result of a macro expansion
62 if (range.first == range.second)
63 return result;
64
65 if (range.first.file != range.second.file) {
66 if (errorMessage)
67 *errorMessage = QStringLiteral("Range spans several files");
68 return result;
69 }
70
71 auto it = m_fileBufferCache.find(range.first.file);
72 if (it == m_fileBufferCache.end()) {
73 const QString fileName = getFileName(range.first.file);
74 if (fileName.isEmpty()) {
75 if (errorMessage)
76 *errorMessage = QStringLiteral("Range has no file");
77 return result;
78 }
79 QFile file(fileName);
80 if (!file.open(QIODevice::ReadOnly)) {
81 if (errorMessage) {
82 QTextStream str(errorMessage);
83 str << "Cannot open \"" << QDir::toNativeSeparators(fileName)
84 << "\": " << file.errorString();
85 }
86 return result;
87 }
88 it = m_fileBufferCache.insert(range.first.file, file.readAll());
89 }
90
91 const unsigned pos = range.first.offset;
92 const unsigned end = range.second.offset;
93 Q_ASSERT(end > pos);
94 const QByteArray &contents = it.value();
95 if (end >= unsigned(contents.size())) {
96 if (errorMessage) {
97 QTextStream str(errorMessage);
98 str << "Range end " << end << " is above size of \""
99 << QDir::toNativeSeparators(getFileName(range.first.file))
100 << "\" (" << contents.size() << ')';
101 }
102 return result;
103 }
104 result.first = contents.constData() + pos;
105 result.second = contents.constData() + end;
106 return result;
107 }
108
109 BaseVisitor::BaseVisitor() = default;
110 BaseVisitor::~BaseVisitor() = default;
111
visitLocation(const CXSourceLocation & location) const112 bool BaseVisitor::visitLocation(const CXSourceLocation &location) const
113 {
114 return clang_Location_isFromMainFile(location) != 0;
115 }
116
cbHandleStartToken(const CXCursor & cursor)117 BaseVisitor::StartTokenResult BaseVisitor::cbHandleStartToken(const CXCursor &cursor)
118 {
119 switch (cursor.kind) {
120 default:
121 break;
122 }
123
124 return startToken(cursor);
125 }
126
cbHandleEndToken(const CXCursor & cursor,StartTokenResult startResult)127 bool BaseVisitor::cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult)
128 {
129 const bool result = startResult != Recurse || endToken(cursor);
130 switch (cursor.kind) {
131 default:
132 break;
133 }
134
135 return result;
136 }
137
getCodeSnippet(const CXCursor & cursor)138 BaseVisitor::CodeSnippet BaseVisitor::getCodeSnippet(const CXCursor &cursor)
139 {
140 QString errorMessage;
141 CodeSnippet result = m_fileCache.getCodeSnippet(cursor, &errorMessage);
142 if (result.first == nullptr && !errorMessage.isEmpty()) {
143 QString message;
144 QTextStream str(&message);
145 str << "Unable to retrieve code snippet \"" << getCursorSpelling(cursor)
146 << "\": " << errorMessage;
147 appendDiagnostic(Diagnostic(message, cursor, CXDiagnostic_Error));
148 }
149 return result;
150 }
151
getCodeSnippetString(const CXCursor & cursor)152 QString BaseVisitor::getCodeSnippetString(const CXCursor &cursor)
153 {
154 CodeSnippet result = getCodeSnippet(cursor);
155 return result.first != nullptr
156 ? QString::fromUtf8(result.first, int(result.second - result.first))
157 : QString();
158 }
159
160 static CXChildVisitResult
visitorCallback(CXCursor cursor,CXCursor,CXClientData clientData)161 visitorCallback(CXCursor cursor, CXCursor /* parent */, CXClientData clientData)
162 {
163 auto *bv = reinterpret_cast<BaseVisitor *>(clientData);
164
165 const CXSourceLocation location = clang_getCursorLocation(cursor);
166 if (!bv->visitLocation(location))
167 return CXChildVisit_Continue;
168
169 const BaseVisitor::StartTokenResult startResult = bv->cbHandleStartToken(cursor);
170 switch (startResult) {
171 case clang::BaseVisitor::Error:
172 return CXChildVisit_Break;
173 case clang::BaseVisitor::Skip:
174 break;
175 case clang::BaseVisitor::Recurse:
176 clang_visitChildren(cursor, visitorCallback, clientData);
177 break;
178 }
179
180 if (!bv->cbHandleEndToken(cursor, startResult))
181 return CXChildVisit_Break;
182
183 return CXChildVisit_Continue;
184 }
185
diagnostics() const186 BaseVisitor::Diagnostics BaseVisitor::diagnostics() const
187 {
188 return m_diagnostics;
189 }
190
setDiagnostics(const Diagnostics & d)191 void BaseVisitor::setDiagnostics(const Diagnostics &d)
192 {
193 m_diagnostics = d;
194 }
195
appendDiagnostic(const Diagnostic & d)196 void BaseVisitor::appendDiagnostic(const Diagnostic &d)
197 {
198 m_diagnostics.append(d);
199 }
200
byteArrayListToFlatArgV(const QByteArrayList & bl)201 static inline const char **byteArrayListToFlatArgV(const QByteArrayList &bl)
202 {
203 const char **result = new const char *[bl.size() + 1];
204 result[bl.size()] = nullptr;
205 std::transform(bl.cbegin(), bl.cend(), result,
206 [] (const QByteArray &a) { return a.constData(); });
207 return result;
208 }
209
msgCreateTranslationUnit(const QByteArrayList & clangArgs,unsigned flags)210 static QByteArray msgCreateTranslationUnit(const QByteArrayList &clangArgs, unsigned flags)
211 {
212 QByteArray result = "clang_parseTranslationUnit2(0x";
213 result += QByteArray::number(flags, 16);
214 const int count = clangArgs.size();
215 result += ", cmd[" + QByteArray::number(count) + "]=";
216 for (int i = 0; i < count; ++i) {
217 const QByteArray &arg = clangArgs.at(i);
218 if (i)
219 result += ' ';
220 const bool quote = arg.contains(' ') || arg.contains('(');
221 if (quote)
222 result += '"';
223 result += arg;
224 if (quote)
225 result += '"';
226 }
227 result += ')';
228 return result;
229 }
230
createTranslationUnit(CXIndex index,const QByteArrayList & args,unsigned flags=0)231 static CXTranslationUnit createTranslationUnit(CXIndex index,
232 const QByteArrayList &args,
233 unsigned flags = 0)
234 {
235 // courtesy qdoc
236 const unsigned defaultFlags = CXTranslationUnit_SkipFunctionBodies
237 | CXTranslationUnit_Incomplete;
238
239 static const QByteArrayList defaultArgs = {
240 #ifndef Q_OS_WIN
241 "-fPIC",
242 #endif
243 #ifdef Q_OS_MACOS
244 "-Wno-expansion-to-defined", // Workaround for warnings in Darwin stdlib, see
245 // https://github.com/darlinghq/darling/issues/204
246 #endif
247 "-Wno-constant-logical-operand"
248 };
249
250 const QByteArrayList clangArgs = emulatedCompilerOptions() + defaultArgs + args;
251 QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs));
252 qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags);
253
254 CXTranslationUnit tu;
255 CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(),
256 clangArgs.size(), nullptr, 0,
257 defaultFlags | flags, &tu);
258 if (err || !tu) {
259 qWarning().noquote().nospace() << "Could not parse "
260 << clangArgs.constLast().constData() << ", error code: " << err;
261 return nullptr;
262 }
263 return tu;
264 }
265
266 /* clangFlags are flags to clang_parseTranslationUnit2() such as
267 * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35)
268 */
269
parse(const QByteArrayList & clangArgs,unsigned clangFlags,BaseVisitor & bv)270 bool parse(const QByteArrayList &clangArgs, unsigned clangFlags, BaseVisitor &bv)
271 {
272 CXIndex index = clang_createIndex(0 /* excludeDeclarationsFromPCH */,
273 1 /* displayDiagnostics */);
274 if (!index) {
275 qWarning() << "clang_createIndex() failed!";
276 return false;
277 }
278
279 CXTranslationUnit translationUnit = createTranslationUnit(index, clangArgs, clangFlags);
280 if (!translationUnit)
281 return false;
282
283 CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
284
285 clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv));
286
287 QVector<Diagnostic> diagnostics = getDiagnostics(translationUnit);
288 diagnostics.append(bv.diagnostics());
289 bv.setDiagnostics(diagnostics);
290
291 const bool ok = maxSeverity(diagnostics) < CXDiagnostic_Error;
292 if (!ok) {
293 QDebug debug = qWarning();
294 debug.noquote();
295 debug.nospace();
296 debug << "Errors in "
297 << QDir::toNativeSeparators(QFile::decodeName(clangArgs.constLast())) << ":\n";
298 for (const Diagnostic &diagnostic : qAsConst(diagnostics))
299 debug << diagnostic << '\n';
300 }
301
302 clang_disposeTranslationUnit(translationUnit);
303 clang_disposeIndex(index);
304 return ok;
305 }
306
307 } // namespace clang
308