diff options
Diffstat (limited to 'sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp')
-rw-r--r-- | sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp new file mode 100644 index 000000000..da6930476 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -0,0 +1,322 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clangparser.h" +#include "clangutils.h" +#include "clangdebugutils.h" +#include "compilersupport.h" + +#include <QtCore/QByteArrayList> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QScopedArrayPointer> +#include <QtCore/QString> + +using namespace Qt::StringLiterals; + +namespace clang { + +QString SourceFileCache::getFileName(CXFile file) +{ + auto it = m_fileNameCache.find(file); + if (it == m_fileNameCache.end()) + it = m_fileNameCache.insert(file, clang::getFileName(file)); + return it.value(); +} + +std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor, + QString *errorMessage) +{ + static const char empty[] = ""; + + if (errorMessage) + errorMessage->clear(); + + const SourceRange range = getCursorRange(cursor); + // Quick check for equal locations: Frequently happens if the code is + // the result of a macro expansion + if (range.first == range.second) + return std::string_view(empty, 0); + + if (range.first.file != range.second.file) { + if (errorMessage) + *errorMessage = "Range spans several files"_L1; + return std::string_view(empty, 0); + } + + auto it = m_fileBufferCache.find(range.first.file); + if (it == m_fileBufferCache.end()) { + const QString fileName = getFileName(range.first.file); + if (fileName.isEmpty()) { + if (errorMessage) + *errorMessage = "Range has no file"_L1; + return std::string_view(empty, 0); + } + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + if (errorMessage) { + QTextStream str(errorMessage); + str << "Cannot open \"" << QDir::toNativeSeparators(fileName) + << "\": " << file.errorString(); + } + return std::string_view(empty, 0); + } + it = m_fileBufferCache.insert(range.first.file, file.readAll()); + } + + const unsigned pos = range.first.offset; + const unsigned end = range.second.offset; + Q_ASSERT(end > pos); + const QByteArray &contents = it.value(); + if (end >= unsigned(contents.size())) { + if (errorMessage) { + QTextStream str(errorMessage); + str << "Range end " << end << " is above size of \"" + << QDir::toNativeSeparators(getFileName(range.first.file)) + << "\" (" << contents.size() << ')'; + } + return std::string_view(empty, 0); + } + + return std::string_view(contents.constData() + pos, end - pos); +} + +BaseVisitor::BaseVisitor() = default; +BaseVisitor::~BaseVisitor() = default; + +bool BaseVisitor::visitLocation(const QString &, LocationType locationType) const +{ + return locationType != LocationType::System; +} + +BaseVisitor::StartTokenResult BaseVisitor::cbHandleStartToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + default: + break; + } + + return startToken(cursor); +} + +bool BaseVisitor::cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult) +{ + const bool result = startResult != Recurse || endToken(cursor); + switch (cursor.kind) { + default: + break; + } + + return result; +} + +std::string_view BaseVisitor::getCodeSnippet(const CXCursor &cursor) +{ + QString errorMessage; + const std::string_view result = m_fileCache.getCodeSnippet(cursor, &errorMessage); + if (result.empty() && !errorMessage.isEmpty()) { + QString message; + QTextStream str(&message); + str << "Unable to retrieve code snippet \"" << getCursorSpelling(cursor) + << "\": " << errorMessage; + appendDiagnostic(Diagnostic(message, cursor, CXDiagnostic_Error)); + } + return result; +} + +bool BaseVisitor::_handleVisitLocation(const CXSourceLocation &location) +{ + CXFile cxFile; // void * + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &cxFile, &line, &column, &offset); + + if (cxFile == m_currentCxFile) // Same file? + return m_visitCurrent; + + const QString fileName = getFileName(cxFile); + + LocationType locationType = LocationType::Unknown; + if (!fileName.isEmpty()) { + if (clang_Location_isFromMainFile(location) != 0) + locationType = LocationType::Main; + else if (clang_Location_isInSystemHeader(location) != 0) + locationType = LocationType::System; + else + locationType = LocationType::Other; + } + + m_currentCxFile = cxFile; + m_visitCurrent = visitLocation(fileName, locationType); + return m_visitCurrent; +} + +QString BaseVisitor::getCodeSnippetString(const CXCursor &cursor) +{ + const std::string_view result = getCodeSnippet(cursor); + return result.empty() + ? QString() + : QString::fromUtf8(result.data(), qsizetype(result.size())); +} + +static CXChildVisitResult + visitorCallback(CXCursor cursor, CXCursor /* parent */, CXClientData clientData) +{ + auto *bv = reinterpret_cast<BaseVisitor *>(clientData); + + const CXSourceLocation location = clang_getCursorLocation(cursor); + if (!bv->_handleVisitLocation(location)) + return CXChildVisit_Continue; + + const BaseVisitor::StartTokenResult startResult = bv->cbHandleStartToken(cursor); + switch (startResult) { + case clang::BaseVisitor::Error: + return CXChildVisit_Break; + case clang::BaseVisitor::Skip: + break; + case clang::BaseVisitor::Recurse: + clang_visitChildren(cursor, visitorCallback, clientData); + break; + } + + if (!bv->cbHandleEndToken(cursor, startResult)) + return CXChildVisit_Break; + + return CXChildVisit_Continue; +} + +BaseVisitor::Diagnostics BaseVisitor::diagnostics() const +{ + return m_diagnostics; +} + +void BaseVisitor::setDiagnostics(const Diagnostics &d) +{ + m_diagnostics = d; +} + +void BaseVisitor::appendDiagnostic(const Diagnostic &d) +{ + m_diagnostics.append(d); +} + +static inline const char **byteArrayListToFlatArgV(const QByteArrayList &bl) +{ + const char **result = new const char *[bl.size() + 1]; + result[bl.size()] = nullptr; + std::transform(bl.cbegin(), bl.cend(), result, + [] (const QByteArray &a) { return a.constData(); }); + return result; +} + +static QByteArray msgCreateTranslationUnit(const QByteArrayList &clangArgs, unsigned flags) +{ + QByteArray result = "clang_parseTranslationUnit2(0x"; + result += QByteArray::number(flags, 16); + const auto count = clangArgs.size(); + result += ", cmd[" + QByteArray::number(count) + "]="; + for (qsizetype i = 0; i < count; ++i) { + const QByteArray &arg = clangArgs.at(i); + if (i) + result += ' '; + const bool quote = arg.contains(' ') || arg.contains('('); + if (quote) + result += '"'; + result += arg; + if (quote) + result += '"'; + } + result += ')'; + return result; +} + +static CXTranslationUnit createTranslationUnit(CXIndex index, + const QByteArrayList &args, + bool addCompilerSupportArguments, + unsigned flags = 0) +{ + // courtesy qdoc + const unsigned defaultFlags = CXTranslationUnit_Incomplete; + + static const QByteArrayList defaultArgs = { +#ifndef Q_OS_WIN + "-fPIC", +#endif +#ifdef Q_OS_MACOS + "-Wno-expansion-to-defined", // Workaround for warnings in Darwin stdlib, see + // https://github.com/darlinghq/darling/issues/204 +#endif + "-Wno-constant-logical-operand", + "-x", + "c++" // Treat .h as C++, not C + }; + + QByteArrayList clangArgs; + if (addCompilerSupportArguments) { + clangArgs += emulatedCompilerOptions(); + clangArgs += defaultArgs; + } + clangArgs += detectVulkan(); + clangArgs += args; + QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs)); + qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags); + + CXTranslationUnit tu; + CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(), + clangArgs.size(), nullptr, 0, + defaultFlags | flags, &tu); + if (err || !tu) { + qWarning().noquote().nospace() << "Could not parse " + << clangArgs.constLast().constData() << ", error code: " << err; + return nullptr; + } + return tu; +} + +/* clangFlags are flags to clang_parseTranslationUnit2() such as + * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35) + */ + +bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, + unsigned clangFlags, BaseVisitor &bv) +{ + CXIndex index = clang_createIndex(0 /* excludeDeclarationsFromPCH */, + 1 /* displayDiagnostics */); + if (!index) { + qWarning() << "clang_createIndex() failed!"; + return false; + } + + CXTranslationUnit translationUnit = + createTranslationUnit(index, clangArgs, addCompilerSupportArguments, + clangFlags); + if (!translationUnit) + return false; + + CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit); + + clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv)); + + QList<Diagnostic> diagnostics = getDiagnostics(translationUnit); + diagnostics.append(bv.diagnostics()); + bv.setDiagnostics(diagnostics); + + const bool ok = maxSeverity(diagnostics) < CXDiagnostic_Error; + if (!ok) { + QDebug debug = qWarning(); + debug.noquote(); + debug.nospace(); + debug << "Errors in " + << QDir::toNativeSeparators(QFile::decodeName(clangArgs.constLast())) << ":\n"; + for (const Diagnostic &diagnostic : std::as_const(diagnostics)) + debug << diagnostic << '\n'; + } + + clang_disposeTranslationUnit(translationUnit); + clang_disposeIndex(index); + return ok; +} + +} // namespace clang |