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