diff options
Diffstat (limited to 'sources/shiboken6/ApiExtractor/clangparser')
10 files changed, 2885 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp new file mode 100644 index 000000000..31e7efb05 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp @@ -0,0 +1,1296 @@ +// 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 "clangbuilder.h" +#include "compilersupport.h" +#include "clangutils.h" +#include "clangdebugutils.h" + +#include <codemodel.h> +#include <reporthandler.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStack> +#include <QtCore/QList> + +#include <cstring> +#include <ctype.h> + +using namespace Qt::StringLiterals; + +namespace clang { + +static inline bool isClassCursor(const CXCursor &c) +{ + return c.kind == CXCursor_ClassDecl || c.kind == CXCursor_StructDecl + || c.kind == CXCursor_ClassTemplate + || c.kind == CXCursor_ClassTemplatePartialSpecialization; +} + +static inline bool isClassOrNamespaceCursor(const CXCursor &c) +{ + return c.kind == CXCursor_Namespace || isClassCursor(c); +} + +static inline bool withinClassDeclaration(const CXCursor &cursor) +{ + return isClassCursor(clang_getCursorLexicalParent(cursor)); +} + +static QString fixTypeName(QString t) +{ + // Fix "Foo &" -> "Foo&", similarly "Bar **" -> "Bar**" + auto pos = t.size() - 1; + for (; pos >= 0 && (t.at(pos) == u'&' || t.at(pos) == u'*'); --pos) {} + if (pos > 0 && t.at(pos) == u' ') + t.remove(pos, 1); + return t; +} + +// Insert template parameter to class name: "Foo<>" -> "Foo<T1>" -> "Foo<T1,T2>" +// This needs to be done immediately when template parameters are encountered since +// the class name "Foo<T1,T2>" is the scope for nested items. +static bool insertTemplateParameterIntoClassName(const QString &parmName, QString *name) +{ + if (Q_UNLIKELY(!name->endsWith(u'>'))) + return false; + const bool needsComma = name->at(name->size() - 2) != u'<'; + const auto insertionPos = name->size() - 1; + name->insert(insertionPos, parmName); + if (needsComma) + name->insert(insertionPos, u','); + return true; +} + +static inline bool insertTemplateParameterIntoClassName(const QString &parmName, + const ClassModelItem &item) +{ + QString name = item->name(); + const bool result = insertTemplateParameterIntoClassName(parmName, &name); + item->setName(name); + return result; +} + +static inline Access accessPolicy(CX_CXXAccessSpecifier access) +{ + Access result = Access::Public; + switch (access) { + case CX_CXXProtected: + result = Access::Protected; + break; + case CX_CXXPrivate: + result = Access::Private; + break; + default: + break; + } + return result; +} + +static bool isSigned(CXTypeKind kind) +{ + switch (kind) { + case CXType_UChar: + case CXType_Char16: + case CXType_Char32: + case CXType_UShort: + case CXType_UInt: + case CXType_ULong: + case CXType_ULongLong: + case CXType_UInt128: + return false; + default: + break; + } + return true; +} + +class BuilderPrivate { +public: + Q_DISABLE_COPY_MOVE(BuilderPrivate) + + enum class SpecialSystemHeader { + None, + Types, + OpenGL, + WhiteListed, + WhiteListedPath + }; + + using CursorClassHash = QHash<CXCursor, ClassModelItem>; + using TypeInfoHash = QHash<CXType, TypeInfo>; + + explicit BuilderPrivate(BaseVisitor *bv) : m_baseVisitor(bv), m_model(new CodeModel) + { + m_scopeStack.push(NamespaceModelItem(new _FileModelItem(m_model))); + } + ~BuilderPrivate() + { + delete m_model; + } + + // Determine scope from top item. Note that the scope list does not necessarily + // match the scope stack in case of forward-declared inner classes whose definition + // appears in the translation unit while the scope is the outer class. + void updateScope() + { + if (m_scopeStack.size() <= 1) + m_scope.clear(); + else + m_scope = m_scopeStack.back()->scope() << m_scopeStack.back()->name(); + } + + void pushScope(const ScopeModelItem &i) + { + m_scopeStack.push(i); + updateScope(); + } + + void popScope() + { + m_scopeStack.back()->purgeClassDeclarations(); + m_scopeStack.pop(); + updateScope(); + } + + bool addClass(const CXCursor &cursor, CodeModel::ClassType t); + FunctionModelItem createFunction(const CXCursor &cursor, + CodeModel::FunctionType t = CodeModel::Normal, + bool isTemplateCode = false); + FunctionModelItem createMemberFunction(const CXCursor &cursor, + bool isTemplateCode = false); + void qualifyConstructor(const CXCursor &cursor); + TypeInfo createTypeInfoUncached(const CXType &type, + bool *cacheable = nullptr) const; + TypeInfo createTypeInfo(const CXType &type) const; + TypeInfo createTypeInfo(const CXCursor &cursor) const + { return createTypeInfo(clang_getCursorType(cursor)); } + void addTemplateInstantiations(const CXType &type, + QString *typeName, + TypeInfo *t) const; + bool addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const; + + void addTypeDef(const CXCursor &cursor, const CXType &cxType); + ClassModelItem currentTemplateClass() const; + void startTemplateTypeAlias(const CXCursor &cursor); + void endTemplateTypeAlias(const CXCursor &typeAliasCursor); + + TemplateParameterModelItem createTemplateParameter(const CXCursor &cursor) const; + TemplateParameterModelItem createNonTypeTemplateParameter(const CXCursor &cursor) const; + void addField(const CXCursor &cursor); + + static QString cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor); + std::pair<QString, ClassModelItem> getBaseClass(CXType type) const; + void addBaseClass(const CXCursor &cursor); + + SpecialSystemHeader specialSystemHeader(const QString &fileName) const; + bool visitHeader(const QString &fileName) const; + static const char *specialSystemHeaderReason(SpecialSystemHeader sh); + + void setFileName(const CXCursor &cursor, _CodeModelItem *item); + + BaseVisitor *m_baseVisitor; + CodeModel *m_model; + + QStack<ScopeModelItem> m_scopeStack; + QStringList m_scope; + // Store all classes by cursor so that base classes can be found and inner + // classes can be correctly parented in case of forward-declared inner classes + // (QMetaObject::Connection) + CursorClassHash m_cursorClassHash; + + mutable TypeInfoHash m_typeInfoHash; // Cache type information + mutable QHash<QString, TemplateTypeAliasModelItem> m_templateTypeAliases; + + ClassModelItem m_currentClass; + EnumModelItem m_currentEnum; + FunctionModelItem m_currentFunction; + ArgumentModelItem m_currentArgument; + VariableModelItem m_currentField; + TemplateTypeAliasModelItem m_currentTemplateTypeAlias; + QStringList m_forceProcessSystemIncludes; // files, like "memory" + QStringList m_forceProcessSystemIncludePaths; // paths, like "/usr/include/Qt/" + QString m_usingTypeRef; // Base classes in "using Base::member;" + bool m_withinUsingDeclaration = false; + + int m_anonymousEnumCount = 0; + CodeModel::FunctionType m_currentFunctionType = CodeModel::Normal; + bool m_withinFriendDecl = false; + mutable QHash<QString, SpecialSystemHeader> m_systemHeaders; +}; + +bool BuilderPrivate::addClass(const CXCursor &cursor, CodeModel::ClassType t) +{ + QString className = getCursorSpelling(cursor); + m_currentClass.reset(new _ClassModelItem(m_model, className)); + setFileName(cursor, m_currentClass.get()); + m_currentClass->setClassType(t); + // Some inner class? Note that it does not need to be (lexically) contained in a + // class since it is possible to forward declare an inner class: + // class QMetaObject { class Connection; } + // class QMetaObject::Connection {} + const CXCursor semPar = clang_getCursorSemanticParent(cursor); + if (isClassCursor(semPar)) { + const CursorClassHash::const_iterator it = m_cursorClassHash.constFind(semPar); + if (it == m_cursorClassHash.constEnd()) { + QString message; + QTextStream(&message) << "Unable to find containing class \"" + << getCursorSpelling(semPar) << "\" of inner class \"" + << className << "\"."; + // PYSIDE-1501: Has been observed to fail for inner class of + // template with separated implementation where a forward + // declaration of the outer template is reported (Boost). + const auto severity = semPar.kind == CXCursor_ClassTemplate + ? CXDiagnostic_Warning : CXDiagnostic_Error; + const Diagnostic d(message, cursor, severity); + qWarning() << d; + m_baseVisitor->appendDiagnostic(d); + return false; + } + const ClassModelItem &containingClass = it.value(); + containingClass->addClass(m_currentClass); + m_currentClass->setScope(containingClass->scope() << containingClass->name()); + } else { + m_currentClass->setScope(m_scope); + m_scopeStack.back()->addClass(m_currentClass); + } + pushScope(m_currentClass); + m_cursorClassHash.insert(cursor, m_currentClass); + return true; +} + +static QString msgCannotDetermineException(const std::string_view &snippetV) +{ + const auto newLine = snippetV.find('\n'); // Multiline noexcept specifications have been found in Qt + const bool truncate = newLine != std::string::npos; + const qsizetype length = qsizetype(truncate ? newLine : snippetV.size()); + QString snippet = QString::fromUtf8(snippetV.data(), length); + if (truncate) + snippet += "..."_L1; + + return u"Cannot determine exception specification: \""_s + snippet + u'"'; +} + +// Return whether noexcept(<value>) throws. noexcept() takes a constexpr value. +// Try to determine the simple cases (true|false) via code snippet. +static ExceptionSpecification computedExceptionSpecificationFromClang(BaseVisitor *bv, + const CXCursor &cursor, + bool isTemplateCode) +{ + const std::string_view snippet = bv->getCodeSnippet(cursor); + if (snippet.empty()) + return ExceptionSpecification::Unknown; // Macro expansion, cannot tell + if (snippet.find("noexcept(false)") != std::string::npos) + return ExceptionSpecification::Throws; + if (snippet.find("noexcept(true)") != std::string::npos) + return ExceptionSpecification::NoExcept; + // Warn about it unless it is some form of template code where it is common + // to have complicated code, which is of no concern to shiboken, like: + // "QList::emplace(T) noexcept(is_pod<T>)". + if (!isTemplateCode && ReportHandler::isDebug(ReportHandler::FullDebug)) { + const Diagnostic d(msgCannotDetermineException(snippet), cursor, CXDiagnostic_Warning); + qWarning() << d; + bv->appendDiagnostic(d); + } + return ExceptionSpecification::Unknown; +} + +static ExceptionSpecification exceptionSpecificationFromClang(BaseVisitor *bv, + const CXCursor &cursor, + bool isTemplateCode) +{ + const auto ce = clang_getCursorExceptionSpecificationType(cursor); + switch (ce) { + case CXCursor_ExceptionSpecificationKind_ComputedNoexcept: + return computedExceptionSpecificationFromClang(bv, cursor, isTemplateCode); + case CXCursor_ExceptionSpecificationKind_BasicNoexcept: + case CXCursor_ExceptionSpecificationKind_DynamicNone: // throw() + case CXCursor_ExceptionSpecificationKind_NoThrow: + return ExceptionSpecification::NoExcept; + case CXCursor_ExceptionSpecificationKind_Dynamic: // throw(t1..) + case CXCursor_ExceptionSpecificationKind_MSAny: // throw(...) + return ExceptionSpecification::Throws; + default: + // CXCursor_ExceptionSpecificationKind_None, + // CXCursor_ExceptionSpecificationKind_Unevaluated, + // CXCursor_ExceptionSpecificationKind_Uninstantiated + break; + } + return ExceptionSpecification::Unknown; +} + +FunctionModelItem BuilderPrivate::createFunction(const CXCursor &cursor, + CodeModel::FunctionType t, + bool isTemplateCode) +{ + QString name = getCursorSpelling(cursor); + // Apply type fixes to "operator X &" -> "operator X&" + if (name.startsWith(u"operator ")) + name = fixTypeName(name); + auto result = std::make_shared<_FunctionModelItem>(m_model, name); + setFileName(cursor, result.get()); + const auto type = clang_getCursorResultType(cursor); + result->setType(createTypeInfo(type)); + result->setScopeResolution(hasScopeResolution(type)); + result->setFunctionType(t); + result->setScope(m_scope); + result->setStatic(clang_Cursor_getStorageClass(cursor) == CX_SC_Static); + result->setExceptionSpecification(exceptionSpecificationFromClang(m_baseVisitor, cursor, isTemplateCode)); + switch (clang_getCursorAvailability(cursor)) { + case CXAvailability_Available: + break; + case CXAvailability_Deprecated: + result->setAttribute(FunctionAttribute::Deprecated); + break; + case CXAvailability_NotAvailable: // "Foo(const Foo&) = delete;" + result->setDeleted(true); + break; + case CXAvailability_NotAccessible: + break; + } + return result; +} + +static inline CodeModel::FunctionType functionTypeFromCursor(const CXCursor &cursor) +{ + CodeModel::FunctionType result = CodeModel::Normal; + switch (cursor.kind) { + case CXCursor_Constructor: + if (clang_CXXConstructor_isCopyConstructor(cursor) != 0) + result = CodeModel::CopyConstructor; + else if (clang_CXXConstructor_isMoveConstructor(cursor) != 0) + result = CodeModel::MoveConstructor; + else + result = CodeModel::Constructor; + break; + case CXCursor_Destructor: + result = CodeModel::Destructor; + break; + default: + break; + } + return result; +} + +FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor, + bool isTemplateCode) +{ + const CodeModel::FunctionType functionType = + m_currentFunctionType == CodeModel::Signal || m_currentFunctionType == CodeModel::Slot + ? m_currentFunctionType // by annotation + : functionTypeFromCursor(cursor); + isTemplateCode |= m_currentClass->name().endsWith(u'>'); + auto result = createFunction(cursor, functionType, isTemplateCode); + result->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + result->setConstant(clang_CXXMethod_isConst(cursor) != 0); + result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0); + result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0); + result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0); + return result; +} + +// For CXCursor_Constructor, on endToken(). +void BuilderPrivate::qualifyConstructor(const CXCursor &cursor) +{ + // Clang does not tell us whether a constructor is explicit, preventing it + // from being used for implicit conversions. Try to guess whether a + // constructor is explicit in the C++99 sense (1 parameter) by checking for + // isConvertingConstructor() == 0. Fixme: The notion of "isConvertingConstructor" + // should be used in the code model instead of "explicit" + if (clang_CXXConstructor_isDefaultConstructor(cursor) == 0 + && m_currentFunction->arguments().size() == 1 + && clang_CXXConstructor_isCopyConstructor(cursor) == 0 + && clang_CXXConstructor_isMoveConstructor(cursor) == 0) { + m_currentFunction->setAttribute(FunctionAttribute::Explicit, + clang_CXXConstructor_isConvertingConstructor(cursor) == 0); + } +} + +TemplateParameterModelItem BuilderPrivate::createTemplateParameter(const CXCursor &cursor) const +{ + return std::make_shared<_TemplateParameterModelItem>(m_model, getCursorSpelling(cursor)); +} + +TemplateParameterModelItem BuilderPrivate::createNonTypeTemplateParameter(const CXCursor &cursor) const +{ + TemplateParameterModelItem result = createTemplateParameter(cursor); + result->setType(createTypeInfo(clang_getCursorType(cursor))); + return result; +} + +// CXCursor_VarDecl, CXCursor_FieldDecl cursors +void BuilderPrivate::addField(const CXCursor &cursor) +{ + auto field = std::make_shared<_VariableModelItem>(m_model, getCursorSpelling(cursor)); + field->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + field->setScope(m_scope); + field->setType(createTypeInfo(cursor)); + field->setMutable(clang_CXXField_isMutable(cursor) != 0); + setFileName(cursor, field.get()); + m_currentField = field; + m_scopeStack.back()->addVariable(field); +} + +// Create qualified name "std::list<std::string>" -> ("std", "list<std::string>") +static QStringList qualifiedName(const QString &t) +{ + QStringList result; + int end = t.indexOf(u'<'); + if (end == -1) + end = t.indexOf(u'('); + if (end == -1) + end = t.size(); + int lastPos = 0; + while (true) { + const int nextPos = t.indexOf(u"::"_s, lastPos); + if (nextPos < 0 || nextPos >= end) + break; + result.append(t.mid(lastPos, nextPos - lastPos)); + lastPos = nextPos + 2; + } + result.append(t.right(t.size() - lastPos)); + return result; +} + +static bool isArrayType(CXTypeKind k) +{ + return k == CXType_ConstantArray || k == CXType_IncompleteArray + || k == CXType_VariableArray || k == CXType_DependentSizedArray; +} + +static bool isPointerType(CXTypeKind k) +{ + return k == CXType_Pointer || k == CXType_LValueReference || k == CXType_RValueReference; +} + +bool BuilderPrivate::addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const +{ + // Template arguments + switch (type.kind) { + case CXType_Elaborated: + case CXType_Record: + case CXType_Unexposed: + if (const int numTemplateArguments = qMax(0, clang_Type_getNumTemplateArguments(type))) { + for (unsigned tpl = 0; tpl < unsigned(numTemplateArguments); ++tpl) { + const CXType argType = clang_Type_getTemplateArgumentAsType(type, tpl); + // CXType_Invalid is returned when hitting on a specialization + // of a non-type template (template <int v>). + if (argType.kind == CXType_Invalid) + return false; + t->addInstantiation(createTypeInfoUncached(argType)); + } + } + break; + default: + break; + } + return true; +} + +static void dummyTemplateArgumentHandler(int, QStringView) {} + +void BuilderPrivate::addTemplateInstantiations(const CXType &type, + QString *typeName, + TypeInfo *t) const +{ + // In most cases, for templates like "Vector<A>", Clang will give us the + // arguments by recursing down the type. However this will fail for example + // within template classes (for functions like the copy constructor): + // template <class T> + // class Vector { + // Vector(const Vector&); + // }; + // In that case, have TypeInfo parse the list from the spelling. + // Finally, remove the list "<>" from the type name. + const bool parsed = addTemplateInstantiationsRecursion(type, t) + && !t->instantiations().isEmpty(); + if (!parsed) + t->setInstantiations({}); + const auto pos = parsed + ? parseTemplateArgumentList(*typeName, dummyTemplateArgumentHandler) + : t->parseTemplateArgumentList(*typeName); + if (pos.first != -1 && pos.second != -1 && pos.second > pos.first) + typeName->remove(pos.first, pos.second - pos.first); +} + +TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type, + bool *cacheable) const +{ + if (type.kind == CXType_Pointer) { // Check for function pointers, first. + const CXType pointeeType = clang_getPointeeType(type); + const int argCount = clang_getNumArgTypes(pointeeType); + if (argCount >= 0) { + TypeInfo result = createTypeInfoUncached(clang_getResultType(pointeeType), + cacheable); + result.setFunctionPointer(true); + for (int a = 0; a < argCount; ++a) + result.addArgument(createTypeInfoUncached(clang_getArgType(pointeeType, unsigned(a)), + cacheable)); + return result; + } + } + + TypeInfo typeInfo; + + CXType nestedType = type; + for (; isArrayType(nestedType.kind); nestedType = clang_getArrayElementType(nestedType)) { + const long long size = clang_getArraySize(nestedType); + typeInfo.addArrayElement(size >= 0 ? QString::number(size) : QString()); + } + + TypeInfo::Indirections indirections; + for (; isPointerType(nestedType.kind); nestedType = clang_getPointeeType(nestedType)) { + switch (nestedType.kind) { + case CXType_Pointer: + indirections.prepend(clang_isConstQualifiedType(nestedType) != 0 + ? Indirection::ConstPointer : Indirection::Pointer); + break; + case CXType_LValueReference: + typeInfo.setReferenceType(LValueReference); + break; + case CXType_RValueReference: + typeInfo.setReferenceType(RValueReference); + break; + default: + break; + } + } + typeInfo.setIndirectionsV(indirections); + + typeInfo.setConstant(clang_isConstQualifiedType(nestedType) != 0); + typeInfo.setVolatile(clang_isVolatileQualifiedType(nestedType) != 0); + + QString typeName = getResolvedTypeName(nestedType); + while (TypeInfo::stripLeadingConst(&typeName) + || TypeInfo::stripLeadingVolatile(&typeName)) { + } + + // For typedefs within templates or nested classes within templates (iterators): + // "template <class T> class QList { using Value=T; .." + // the typedef source is named "type-parameter-0-0". Convert it back to the + // template parameter name. The CXTypes are the same for all templates and + // must not be cached. + if (m_currentClass && typeName.startsWith(u"type-parameter-0-")) { + if (cacheable != nullptr) + *cacheable = false; + bool ok; + const int n = QStringView{typeName}.mid(17).toInt(&ok); + if (ok) { + auto currentTemplate = currentTemplateClass(); + if (currentTemplate && n < currentTemplate->templateParameters().size()) + typeName = currentTemplate->templateParameters().at(n)->name(); + } + } + + // Obtain template instantiations if the name has '<' (thus excluding + // typedefs like "std::string". + if (typeName.contains(u'<')) + addTemplateInstantiations(nestedType, &typeName, &typeInfo); + + typeInfo.setQualifiedName(qualifiedName(typeName)); + // 3320:CINDEX_LINKAGE int clang_getNumArgTypes(CXType T); function ptr types? + typeInfo.simplifyStdType(); + return typeInfo; +} + +TypeInfo BuilderPrivate::createTypeInfo(const CXType &type) const +{ + const auto it = m_typeInfoHash.constFind(type); + if (it != m_typeInfoHash.constEnd()) + return it.value(); + bool cacheable = true; + TypeInfo result = createTypeInfoUncached(type, &cacheable); + if (cacheable) + m_typeInfoHash.insert(type, result); + return result; +} + +void BuilderPrivate::addTypeDef(const CXCursor &cursor, const CXType &cxType) +{ + const QString target = getCursorSpelling(cursor); + auto item = std::make_shared<_TypeDefModelItem>(m_model, target); + setFileName(cursor, item.get()); + item->setType(createTypeInfo(cxType)); + item->setScope(m_scope); + m_scopeStack.back()->addTypeDef(item); +} + +ClassModelItem BuilderPrivate::currentTemplateClass() const +{ + for (auto i = m_scopeStack.size() - 1; i >= 0; --i) { + auto klass = std::dynamic_pointer_cast<_ClassModelItem>(m_scopeStack.at(i)); + if (klass && klass->isTemplate()) + return klass; + } + return {}; +} + +void BuilderPrivate::startTemplateTypeAlias(const CXCursor &cursor) +{ + const QString target = getCursorSpelling(cursor); + m_currentTemplateTypeAlias.reset(new _TemplateTypeAliasModelItem(m_model, target)); + setFileName(cursor, m_currentTemplateTypeAlias.get()); + m_currentTemplateTypeAlias->setScope(m_scope); +} + +void BuilderPrivate::endTemplateTypeAlias(const CXCursor &typeAliasCursor) +{ + CXType type = clang_getTypedefDeclUnderlyingType(typeAliasCursor); + // Usually "<elaborated>std::list<T>" or "<unexposed>Container1<T>", + // as obtained with parser of PYSIDE-323 + if (type.kind == CXType_Unexposed || type.kind == CXType_Elaborated) { + m_currentTemplateTypeAlias->setType(createTypeInfo(type)); + m_scopeStack.back()->addTemplateTypeAlias(m_currentTemplateTypeAlias); + } + m_currentTemplateTypeAlias.reset(); +} + +// extract an expression from the cursor via source +// CXCursor_EnumConstantDecl, ParmDecl (a = Flag1 | Flag2) +QString BuilderPrivate::cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor) +{ + const std::string_view snippet = bv->getCodeSnippet(cursor); + auto equalSign = snippet.find('='); + if (equalSign == std::string::npos) + return QString(); + ++equalSign; + QString result = QString::fromLocal8Bit(snippet.data() + equalSign, + qsizetype(snippet.size() - equalSign)); + // Fix a default expression as read from code. Simplify white space + result.remove(u'\r'); + return result.contains(u'"') ? result.trimmed() : result.simplified(); +} + +// Resolve a type (loop over aliases/typedefs), for example for base classes +// Note: TypeAliasTemplateDecl ("using QVector<T>=QList<T>") is automatically +// resolved by clang_getTypeDeclaration(), but it stops at +// TypeAliasDecl / TypedefDecl. + +struct TypeDeclaration +{ + CXType type; + CXCursor declaration; +}; + +static inline bool isTypeAliasDecl(const CXCursor &cursor) +{ + const auto kind = clang_getCursorKind(cursor); + return kind == CXCursor_TypeAliasDecl || kind == CXCursor_TypedefDecl; +} + +static TypeDeclaration resolveBaseClassType(CXType type) +{ + CXCursor decl = clang_getTypeDeclaration(type); + auto resolvedType = clang_getCursorType(decl); + if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind) + type = resolvedType; + while (isTypeAliasDecl(decl)) { + type = clang_getTypedefDeclUnderlyingType(decl); + decl = clang_getTypeDeclaration(type); + } + return {type, decl}; +} + +// Note: Return the baseclass for cursors like CXCursor_CXXBaseSpecifier, +// where the cursor spelling has "struct baseClass". +std::pair<QString, ClassModelItem> BuilderPrivate::getBaseClass(CXType type) const +{ + const auto decl = resolveBaseClassType(type); + // Note: spelling has "struct baseClass", use type + QString baseClassName = getTypeName(decl.type); + if (baseClassName.startsWith(u"std::")) // Simplify "std::" types + baseClassName = createTypeInfo(decl.type).toString(); + + auto it = m_cursorClassHash.constFind(decl.declaration); + // Not found: Set unqualified name. This happens in cases like + // "class X : public std::list<...>", "template<class T> class Foo : public T" + // and standard types like true_type, false_type. + if (it == m_cursorClassHash.constEnd()) + return {baseClassName, {}}; + + // Completely qualify the class name by looking it up and taking its scope + // plus the actual baseClass stripped off any scopes. Consider: + // namespace std { + // template <class T> class vector {}; + // namespace n { + // class Foo : public vector<int> {}; + // } + // } + // should have "std::vector<int>" as base class (whereas the type of the base class is + // "std::vector<T>"). + const QStringList &baseScope = it.value()->scope(); + if (!baseScope.isEmpty()) { + const int lastSep = baseClassName.lastIndexOf(u"::"_s); + if (lastSep >= 0) + baseClassName.remove(0, lastSep + u"::"_s.size()); + baseClassName.prepend(u"::"_s); + baseClassName.prepend(baseScope.join(u"::"_s)); + } + return {baseClassName, it.value()}; +} + +// Add a base class to the current class from CXCursor_CXXBaseSpecifier +void BuilderPrivate::addBaseClass(const CXCursor &cursor) +{ + Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_CXXBaseSpecifier); + const auto access = accessPolicy(clang_getCXXAccessSpecifier(cursor)); + const auto baseClass = getBaseClass(clang_getCursorType(cursor)); + m_currentClass->addBaseClass({baseClass.first, baseClass.second, access}); +} + +void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item) +{ + const SourceRange range = getCursorRange(cursor); + QString file = m_baseVisitor->getFileName(range.first.file); + if (!file.isEmpty()) { // Has been observed to be 0 for invalid locations + item->setFileName(QDir::cleanPath(file)); + item->setStartPosition(int(range.first.line), int(range.first.column)); + item->setEndPosition(int(range.second.line), int(range.second.column)); + } +} + +Builder::Builder() +{ + d = new BuilderPrivate(this); +} + +Builder::~Builder() +{ + delete d; +} + +static QString baseName(QString path) +{ + qsizetype lastSlash = path.lastIndexOf(u'/'); +#ifdef Q_OS_WIN + if (lastSlash < 0) + lastSlash = path.lastIndexOf(u'\\'); +#endif + if (lastSlash > 0) + path.remove(0, lastSlash + 1); + return path; +} + +const char * BuilderPrivate::specialSystemHeaderReason(BuilderPrivate::SpecialSystemHeader sh) +{ + static const QHash<SpecialSystemHeader, const char *> mapping { + {SpecialSystemHeader::OpenGL, "OpenGL"}, + {SpecialSystemHeader::Types, "types"}, + {SpecialSystemHeader::WhiteListed, "white listed"}, + {SpecialSystemHeader::WhiteListedPath, "white listed path"} + }; + return mapping.value(sh, ""); +} + +bool BuilderPrivate::visitHeader(const QString &fileName) const +{ + auto it = m_systemHeaders.find(fileName); + if (it == m_systemHeaders.end()) { + it = m_systemHeaders.insert(fileName, specialSystemHeader(fileName)); + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) { + const QString &name = QDir::toNativeSeparators(fileName); + if (it.value() == SpecialSystemHeader::None) { + qCInfo(lcShiboken, "Skipping system header %s", qPrintable(name)); + } else { + qCInfo(lcShiboken, "Parsing system header %s (%s)", + qPrintable(name), specialSystemHeaderReason(it.value())); + } + } + } + return it.value() != SpecialSystemHeader::None; +} + +BuilderPrivate::SpecialSystemHeader + BuilderPrivate::specialSystemHeader(const QString &fileName) const +{ + // Resolve OpenGL typedefs although the header is considered a system header. + const QString baseName = clang::baseName(fileName); + if (baseName == u"gl.h" + || baseName == u"gl2.h" + || baseName == u"gl3.h" + || baseName == u"gl31.h" + || baseName == u"gl32.h" + || baseName == u"stdint.h" // Windows: int32_t, uint32_t + || baseName == u"stddef.h") { // size_t` + return SpecialSystemHeader::OpenGL; + } + + switch (clang::platform()) { + case Platform::Unix: + if (fileName == u"/usr/include/stdlib.h" + || baseName == u"types.h" + || baseName == u"stdint-intn.h" // int32_t + || baseName == u"stdint-uintn.h") { // uint32_t + return SpecialSystemHeader::Types; + } + break; + case Platform::macOS: + // Parse the following system headers to get the correct typdefs for types like + // int32_t, which are used in the macOS implementation of OpenGL framework. + // They are installed under /Applications/Xcode.app/Contents/Developer/Platforms... + if (baseName == u"gltypes.h" + || fileName.contains(u"/usr/include/_types") + || fileName.contains(u"/usr/include/sys/_types")) { + return SpecialSystemHeader::Types; + } + break; + default: + break; + } + + // When building against system Qt (as it happens with yocto / Boot2Qt), the Qt headers are + // considered system headers by clang_Location_isInSystemHeader, and shiboken will not + // process them. We need to explicitly process them by checking against the list of + // include paths that were passed to shiboken's --force-process-system-include-paths option + // or specified via the <system-include> xml tag. + if (m_forceProcessSystemIncludes.contains(baseName)) + return SpecialSystemHeader::WhiteListed; + + if (std::any_of(m_forceProcessSystemIncludePaths.cbegin(), + m_forceProcessSystemIncludePaths.cend(), + [fileName](const QString &p) { return fileName.startsWith(p); })) { + return SpecialSystemHeader::WhiteListedPath; + } + + return SpecialSystemHeader::None; +} + +bool Builder::visitLocation(const QString &fileName, LocationType locationType) const +{ + return locationType != LocationType::System || d->visitHeader(fileName); +} + +void Builder::setForceProcessSystemIncludes(const QStringList &systemIncludes) +{ + for (const auto &i : systemIncludes) { + QFileInfo fi(i); + if (fi.exists() && fi.isDir()) + d->m_forceProcessSystemIncludePaths.append(i); + else + d->m_forceProcessSystemIncludes.append(i); + } +} + +FileModelItem Builder::dom() const +{ + Q_ASSERT(!d->m_scopeStack.isEmpty()); + auto rootScope = d->m_scopeStack.constFirst(); + rootScope->purgeClassDeclarations(); + return std::dynamic_pointer_cast<_FileModelItem>(rootScope); +} + +static QString msgOutOfOrder(const CXCursor &cursor, const char *expectedScope) +{ + return getCursorKindName(cursor.kind) + u' ' + + getCursorSpelling(cursor) + u" encountered outside "_s + + QLatin1StringView(expectedScope) + u'.'; +} + +static CodeModel::ClassType codeModelClassTypeFromCursor(CXCursorKind kind) +{ + CodeModel::ClassType result = CodeModel::Class; + if (kind == CXCursor_UnionDecl) + result = CodeModel::Union; + else if (kind == CXCursor_StructDecl) + result = CodeModel::Struct; + return result; +} + +static NamespaceType namespaceType(const CXCursor &cursor) +{ + if (clang_Cursor_isAnonymous(cursor)) + return NamespaceType::Anonymous; +#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 59 + if (clang_Cursor_isInlineNamespace(cursor)) + return NamespaceType::Inline; +#endif + return NamespaceType::Default; +} + +static QString enumType(const CXCursor &cursor) +{ + QString name = getCursorSpelling(cursor); // "enum Foo { v1, v2 };" + if (name.contains(u"unnamed enum")) // Clang 16.0 + return {}; + if (name.isEmpty()) { + // PYSIDE-1228: For "typedef enum { v1, v2 } Foo;", type will return + // "Foo" as expected. Care must be taken to exclude real anonymous enums. + name = getTypeName(clang_getCursorType(cursor)); + if (name.contains(u"(unnamed") // Clang 12.0.1 + || name.contains(u"(anonymous")) { // earlier + name.clear(); + } + } + return name; +} + +BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + case CXCursor_CXXAccessSpecifier: + d->m_currentFunctionType = CodeModel::Normal; + break; + case CXCursor_AnnotateAttr: { + const QString annotation = getCursorSpelling(cursor); + if (annotation == u"qt_slot") + d->m_currentFunctionType = CodeModel::Slot; + else if (annotation == u"qt_signal") + d->m_currentFunctionType = CodeModel::Signal; + else + d->m_currentFunctionType = CodeModel::Normal; + } + break; + case CXCursor_CXXBaseSpecifier: + if (!d->m_currentClass) { + const Diagnostic d(msgOutOfOrder(cursor, "class"), cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + d->addBaseClass(cursor); + break; + case CXCursor_ClassDecl: + case CXCursor_UnionDecl: + case CXCursor_StructDecl: + if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0 + || !d->addClass(cursor, codeModelClassTypeFromCursor(cursor.kind))) { + return Skip; + } + break; + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0 + || !d->addClass(cursor, CodeModel::Class)) { + return Skip; + } + d->m_currentClass->setName(d->m_currentClass->name() + "<>"_L1); + d->m_scope.back() += "<>"_L1; + break; + case CXCursor_EnumDecl: { + QString name = enumType(cursor); + EnumKind kind = CEnum; + if (name.isEmpty()) { + kind = AnonymousEnum; + name = "enum_"_L1 + QString::number(++d->m_anonymousEnumCount); +#if !CLANG_NO_ENUMDECL_ISSCOPED + } else if (clang_EnumDecl_isScoped(cursor) != 0) { +#else + } else if (clang_EnumDecl_isScoped4(this, cursor) != 0) { +#endif + kind = EnumClass; + } + d->m_currentEnum.reset(new _EnumModelItem(d->m_model, name)); + d->setFileName(cursor, d->m_currentEnum.get()); + d->m_currentEnum->setScope(d->m_scope); + d->m_currentEnum->setEnumKind(kind); + if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated) + d->m_currentEnum->setDeprecated(true); + const auto enumType = fullyResolveType(clang_getEnumDeclIntegerType(cursor)); + d->m_currentEnum->setSigned(isSigned(enumType.kind)); + d->m_currentEnum->setUnderlyingType(getTypeName(enumType)); + if (std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back())) + d->m_currentEnum->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + } + break; + case CXCursor_EnumConstantDecl: { + const QString name = getCursorSpelling(cursor); + if (!d->m_currentEnum) { + const Diagnostic d(msgOutOfOrder(cursor, "enum"), cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + EnumValue enumValue; + if (d->m_currentEnum->isSigned()) + enumValue.setValue(clang_getEnumConstantDeclValue(cursor)); + else + enumValue.setUnsignedValue(clang_getEnumConstantDeclUnsignedValue(cursor)); + auto enumConstant = std::make_shared<_EnumeratorModelItem>(d->m_model, name); + enumConstant->setStringValue(d->cursorValueExpression(this, cursor)); + enumConstant->setValue(enumValue); + if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated) + enumConstant->setDeprecated(true); + d->m_currentEnum->addEnumerator(enumConstant); + } + break; + case CXCursor_VarDecl: + // static class members are seen as CXCursor_VarDecl + if (isClassOrNamespaceCursor(clang_getCursorSemanticParent(cursor))) { + d->addField(cursor); + d->m_currentField->setStatic(true); + } + break; + case CXCursor_FieldDecl: + d->addField(cursor); + break; + case CXCursor_FriendDecl: + d->m_withinFriendDecl = true; + break; + case CXCursor_CompoundStmt: // Function bodies + return Skip; + case CXCursor_Constructor: + case CXCursor_Destructor: // Note: Also use clang_CXXConstructor_is..Constructor? + case CXCursor_CXXMethod: + case CXCursor_ConversionFunction: + // Member functions of other classes can be declared to be friends. + // Skip inline member functions outside class, only go by declarations inside class + if (d->m_withinFriendDecl || !withinClassDeclaration(cursor)) + return Skip; + d->m_currentFunction = d->createMemberFunction(cursor, false); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + // Not fully supported, currently, seen as normal function + // Note: May appear inside class (member template) or outside (free template). + case CXCursor_FunctionTemplate: { + const CXCursor semParent = clang_getCursorSemanticParent(cursor); + if (isClassCursor(semParent)) { + if (semParent == clang_getCursorLexicalParent(cursor)) { + d->m_currentFunction = d->createMemberFunction(cursor, true); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + } + return Skip; // inline member functions outside class + } + } + d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, true); + d->setFileName(cursor, d->m_currentFunction.get()); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + case CXCursor_FunctionDecl: + // Free functions or functions completely defined within "friend" (class + // operators). Note: CXTranslationUnit_SkipFunctionBodies must be off for + // clang_isCursorDefinition() to work here. + if (!d->m_withinFriendDecl || clang_isCursorDefinition(cursor) != 0) { + auto scope = d->m_scopeStack.size() - 1; // enclosing class + if (d->m_withinFriendDecl) { + // Friend declaration: go back to namespace or file scope. + for (--scope; d->m_scopeStack.at(scope)->kind() == _CodeModelItem::Kind_Class; --scope) { + } + } + d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, false); + d->m_currentFunction->setHiddenFriend(d->m_withinFriendDecl); + d->m_scopeStack.at(scope)->addFunction(d->m_currentFunction); + } + break; + case CXCursor_Namespace: { + const auto type = namespaceType(cursor); + if (type == NamespaceType::Anonymous) + return Skip; + const QString name = getCursorSpelling(cursor); + const auto parentNamespaceItem = std::dynamic_pointer_cast<_NamespaceModelItem>(d->m_scopeStack.back()); + if (!parentNamespaceItem) { + const QString message = msgOutOfOrder(cursor, "namespace") + + u" (current scope: "_s + d->m_scopeStack.back()->name() + u')'; + const Diagnostic d(message, cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + // Treat namespaces separately to allow for extending namespaces + // in subsequent modules. + NamespaceModelItem namespaceItem = parentNamespaceItem->findNamespace(name); + namespaceItem.reset(new _NamespaceModelItem(d->m_model, name)); + d->setFileName(cursor, namespaceItem.get()); + namespaceItem->setScope(d->m_scope); + namespaceItem->setType(type); + parentNamespaceItem->addNamespace(namespaceItem); + d->pushScope(namespaceItem); + } + break; + case CXCursor_ParmDecl: + // Skip in case of nested CXCursor_ParmDecls in case one parameter is a function pointer + // and function pointer typedefs. + if (!d->m_currentArgument && d->m_currentFunction) { + const QString name = getCursorSpelling(cursor); + d->m_currentArgument.reset(new _ArgumentModelItem(d->m_model, name)); + const auto type = clang_getCursorType(cursor); + d->m_currentArgument->setScopeResolution(hasScopeResolution(type)); + d->m_currentArgument->setType(d->createTypeInfo(type)); + d->m_currentFunction->addArgument(d->m_currentArgument); + QString defaultValueExpression = d->cursorValueExpression(this, cursor); + if (!defaultValueExpression.isEmpty()) { + d->m_currentArgument->setDefaultValueExpression(defaultValueExpression); + d->m_currentArgument->setDefaultValue(true); + } + } else { + return Skip; + } + break; + case CXCursor_TemplateTypeParameter: + case CXCursor_NonTypeTemplateParameter: { + const TemplateParameterModelItem tItem = cursor.kind == CXCursor_TemplateTemplateParameter + ? d->createTemplateParameter(cursor) : d->createNonTypeTemplateParameter(cursor); + // Apply to function/member template? + if (d->m_currentFunction) { + d->m_currentFunction->setTemplateParameters(d->m_currentFunction->templateParameters() << tItem); + } else if (d->m_currentTemplateTypeAlias) { + d->m_currentTemplateTypeAlias->addTemplateParameter(tItem); + } else if (d->m_currentClass) { // Apply to class + const QString &tplParmName = tItem->name(); + if (Q_UNLIKELY(!insertTemplateParameterIntoClassName(tplParmName, d->m_currentClass) + || !insertTemplateParameterIntoClassName(tplParmName, &d->m_scope.back()))) { + const QString message = "Error inserting template parameter \""_L1 + tplParmName + + "\" into "_L1 + d->m_currentClass->name(); + const Diagnostic d(message, cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + d->m_currentClass->setTemplateParameters(d->m_currentClass->templateParameters() << tItem); + } + } + break; + case CXCursor_TypeAliasTemplateDecl: + d->startTemplateTypeAlias(cursor); + break; + case CXCursor_TypeAliasDecl: // May contain nested CXCursor_TemplateTypeParameter + if (!d->m_currentTemplateTypeAlias) { + const CXType type = clang_getCanonicalType(clang_getCursorType(cursor)); + if (type.kind > CXType_Unexposed) + d->addTypeDef(cursor, type); + return Skip; + } else { + d->endTemplateTypeAlias(cursor); + } + break; + case CXCursor_TypedefDecl: { + auto underlyingType = clang_getTypedefDeclUnderlyingType(cursor); + d->addTypeDef(cursor, underlyingType); + // For "typedef enum/struct {} Foo;", skip the enum/struct + // definition nested into the typedef (PYSIDE-1228). + if (underlyingType.kind == CXType_Elaborated) + return Skip; + } + break; + // Using declarations look as follows: + // 1) Normal, non-template case ("using QObject::parent"): UsingDeclaration, TypeRef + // 2) Simple template case ("using QList::append()"): UsingDeclaration, TypeRef "QList<T>" + // 3) Template case with parameters ("using QList<T>::append()"): + // UsingDeclaration, TemplateRef "QList", TypeRef "T" + case CXCursor_TemplateRef: + if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty()) + d->m_usingTypeRef = getCursorSpelling(cursor); + break; + case CXCursor_TypeRef: + if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty()) + d->m_usingTypeRef = d->getBaseClass(clang_getCursorType(cursor)).first; + break; + case CXCursor_CXXFinalAttr: + if (d->m_currentFunction) + d->m_currentFunction->setAttribute(FunctionAttribute::Final); + else if (d->m_currentClass) + d->m_currentClass->setFinal(true); + break; + case CXCursor_CXXOverrideAttr: + if (d->m_currentFunction) + d->m_currentFunction->setAttribute(FunctionAttribute::Override); + break; + case CXCursor_StaticAssert: + // Check for Q_PROPERTY() (see PySide6/global.h.in for an explanation + // how it is defined, and qdoc). + if (clang_isDeclaration(cursor.kind) && d->m_currentClass) { + auto snippet = getCodeSnippet(cursor); + const auto length = snippet.size(); + if (length > 12 && *snippet.rbegin() == ')' + && snippet.compare(0, 11, "Q_PROPERTY(") == 0) { + const QString qProperty = QString::fromUtf8(snippet.data() + 11, length - 12); + d->m_currentClass->addPropertyDeclaration(qProperty); + } + } + break; + // UsingDeclaration: consists of a TypeRef (base) and OverloadedDeclRef (member name) + case CXCursor_UsingDeclaration: + if (d->m_currentClass) + d->m_withinUsingDeclaration = true; + break; + case CXCursor_OverloadedDeclRef: + if (d->m_withinUsingDeclaration && !d->m_usingTypeRef.isEmpty()) { + QString member = getCursorSpelling(cursor); + if (member == d->m_currentClass->name()) + member = d->m_usingTypeRef; // Overloaded member is Constructor, use base + const auto ap = accessPolicy(clang_getCXXAccessSpecifier(cursor)); + d->m_currentClass->addUsingMember(d->m_usingTypeRef, member, ap); + } + break; + default: + break; + } + return BaseVisitor::Recurse; +} + +bool Builder::endToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + case CXCursor_UnionDecl: + case CXCursor_ClassDecl: + case CXCursor_StructDecl: + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + d->popScope(); + // Continue in outer class after leaving inner class? + if (auto lastClass = std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back())) + d->m_currentClass = lastClass; + else + d->m_currentClass.reset(); + d->m_currentFunctionType = CodeModel::Normal; + break; + case CXCursor_EnumDecl: + if (d->m_currentEnum) + d->m_scopeStack.back()->addEnum(d->m_currentEnum); + d->m_currentEnum.reset(); + break; + case CXCursor_FriendDecl: + d->m_withinFriendDecl = false; + break; + case CXCursor_VarDecl: + case CXCursor_FieldDecl: + d->m_currentField.reset(); + break; + case CXCursor_Constructor: + d->qualifyConstructor(cursor); + if (d->m_currentFunction) { + d->m_currentFunction->_determineType(); + d->m_currentFunction.reset(); + } + break; + case CXCursor_Destructor: + case CXCursor_CXXMethod: + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + if (d->m_currentFunction) { + d->m_currentFunction->_determineType(); + d->m_currentFunction.reset(); + } + break; + case CXCursor_ConversionFunction: + if (d->m_currentFunction) { + d->m_currentFunction->setFunctionType(CodeModel::ConversionOperator); + d->m_currentFunction.reset(); + } + break; + case CXCursor_Namespace: + d->popScope(); + break; + case CXCursor_ParmDecl: + d->m_currentArgument.reset(); + break; + case CXCursor_TypeAliasTemplateDecl: + d->m_currentTemplateTypeAlias.reset(); + break; + case CXCursor_UsingDeclaration: + d->m_withinUsingDeclaration = false; + d->m_usingTypeRef.clear(); + break; + default: + break; + } + return true; +} + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h new file mode 100644 index 000000000..b2ec6d304 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h @@ -0,0 +1,37 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGBUILDER_H +#define CLANGBUILDER_H + +#include "clangparser.h" + +#include <codemodel_fwd.h> + +namespace clang { + +class BuilderPrivate; + +class Builder : public BaseVisitor { +public: + Q_DISABLE_COPY_MOVE(Builder) + + Builder(); + ~Builder(); + + void setForceProcessSystemIncludes(const QStringList &systemIncludes); + + bool visitLocation(const QString &fileName, LocationType locationType) const override; + + StartTokenResult startToken(const CXCursor &cursor) override; + bool endToken(const CXCursor &cursor) override; + + FileModelItem dom() const; + +private: + BuilderPrivate *d; +}; + +} // namespace clang + +#endif // CLANGBUILDER_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp new file mode 100644 index 000000000..3c002da9c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp @@ -0,0 +1,175 @@ +// 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 "clangdebugutils.h" +#include "clangutils.h" + +#include <QtCore/QDebug> +#include <QtCore/QString> + +#ifndef QT_NO_DEBUG_STREAM + +#ifdef Q_OS_WIN +const char pathSep = '\\'; +#else +const char pathSep = '/'; +#endif + +static const char *baseName(const char *fileName) +{ + const char *b = std::strrchr(fileName, pathSep); + return b ? b + 1 : fileName; +} + +QDebug operator<<(QDebug s, const CXString &cs) +{ + s << clang_getCString(cs); + return s; +} + +QDebug operator<<(QDebug s, CXCursorKind cursorKind) // Enum +{ + const CXString kindName = clang_getCursorKindSpelling(cursorKind); + s << kindName; + clang_disposeString(kindName); + return s; +} + +static const char *accessSpecsStrings[] +{ + // CX_CXXInvalidAccessSpecifier, CX_CXXPublic, CX_CXXProtected, CX_CXXPrivate + "invalid", "public", "protected", "private" +}; + +QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac) +{ + s << accessSpecsStrings[ac]; + return s; +} + +struct formatCXTypeName +{ + explicit formatCXTypeName(const CXType &type) : m_type(type) {} + + const CXType &m_type; +}; + +QDebug operator<<(QDebug debug, const formatCXTypeName &ft) +{ + CXString typeSpelling = clang_getTypeSpelling(ft.m_type); + debug << typeSpelling; + clang_disposeString(typeSpelling); + return debug; +} + +QDebug operator<<(QDebug debug, const CXType &type) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + debug << "CXType("; + if (type.kind == CXType_Invalid) { + debug << "invalid)"; + return debug; + } + + debug << type.kind; + switch (type.kind) { + case CXType_Unexposed: + debug << " [unexposed]"; + break; + case CXType_Elaborated: + debug << " [elaborated]"; + break; + default: + break; + } + debug << ", " << formatCXTypeName(type) << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const CXCursor &cursor) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + const CXCursorKind kind = clang_getCursorKind(cursor); + debug << "CXCursor("; + if (kind >= CXCursor_FirstInvalid && kind <= CXCursor_LastInvalid) { + debug << "invalid)"; + return debug; + } + + const QString cursorSpelling = clang::getCursorSpelling(cursor); + debug << '"' << cursorSpelling << '"'; + CXString cursorDisplay = clang_getCursorDisplayName(cursor); + if (const char *dpy = clang_getCString(cursorDisplay)) { + const QString display = QString::fromUtf8(dpy); + if (display != cursorSpelling) + debug << ", display=\"" << dpy << '"'; + } + clang_disposeString(cursorDisplay); + + debug << ", kind=" << kind; + + const CXType type = clang_getCursorType(cursor); + switch (kind) { + case CXCursor_CXXAccessSpecifier: + debug << ", " << clang_getCXXAccessSpecifier(cursor); + break; + case CXCursor_CXXBaseSpecifier: + debug << ", inherits=\"" << clang::getCursorSpelling(clang_getTypeDeclaration(type)) << '"'; + break; + case CXCursor_CXXMethod: + case CXCursor_FunctionDecl: + case CXCursor_ConversionFunction: + debug << ", result type=\"" + << formatCXTypeName(clang_getCursorResultType(cursor)) << '"'; + break; + case CXCursor_TypedefDecl: + debug << ", underlyingType=\"" + << formatCXTypeName(clang_getTypedefDeclUnderlyingType(cursor)) << '"'; + break; + default: + break; + } + + debug << ", type=\"" << formatCXTypeName(type) << '"'; + if (clang_Cursor_hasAttrs(cursor)) + debug << ", [attrs]"; + + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug s, const CXSourceLocation &location) +{ + QDebugStateSaver saver(s); + s.nospace(); + CXFile file; // void * + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &file, &line, &column, &offset); + const CXString cxFileName = clang_getFileName(file); + // Has been observed to be 0 for invalid locations + if (const char *cFileName = clang_getCString(cxFileName)) + s << baseName(cFileName) << ':'; + s << line << ':' << column; + clang_disposeString(cxFileName); + return s; +} + +QDebug operator<<(QDebug s, const std::string_view &v) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << '"'; + for (auto c : v) + s << c; + s << '"'; + return s; +} + +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h new file mode 100644 index 000000000..7aac8a575 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h @@ -0,0 +1,26 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGDEBUGUTILS_H +#define CLANGDEBUGUTILS_H + +#include <QtCore/qtclasshelpermacros.h> + +#include <clang-c/Index.h> + +#include <string_view> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QString) + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug s, const CXString &cs); +QDebug operator<<(QDebug s, CXCursorKind cursorKind); +QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac); +QDebug operator<<(QDebug s, const CXType &t); +QDebug operator<<(QDebug s, const CXCursor &cursor); +QDebug operator<<(QDebug s, const CXSourceLocation &location); +QDebug operator<<(QDebug s, const std::string_view &v); // for code snippets +#endif // !QT_NO_DEBUG_STREAM + +#endif // CLANGDEBUGUTILS_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp new file mode 100644 index 000000000..6c0cf3ae2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -0,0 +1,323 @@ +// 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, + LanguageLevel level, + 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(level); + 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, + LanguageLevel level, 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, + level, 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 diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.h b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h new file mode 100644 index 000000000..22e0a50cd --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h @@ -0,0 +1,88 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGPARSER_H +#define CLANGPARSER_H + +#include <clang-c/Index.h> + +#include <QtCore/QByteArrayList> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QList> + +#include <string_view> +#include <utility> + +enum class LanguageLevel; + +namespace clang { + +struct Diagnostic; + +class SourceFileCache { +public: + std::string_view getCodeSnippet(const CXCursor &cursor, QString *errorMessage = nullptr); + QString getFileName(CXFile file); + +private: + using FileBufferCache = QHash<CXFile, QByteArray>; + using FileNameCache = QHash<CXFile, QString>; + + FileBufferCache m_fileBufferCache; + FileNameCache m_fileNameCache; +}; + +enum class LocationType +{ + Main, // Main header parsed for bindings + Other, // A header parsed for bindings + System, // A system header + Unknown // Clang internal +}; + +class BaseVisitor { + Q_DISABLE_COPY_MOVE(BaseVisitor) +public: + using Diagnostics = QList<Diagnostic>; + + enum StartTokenResult { Error, Skip, Recurse }; + + BaseVisitor(); + virtual ~BaseVisitor(); + + // Whether location should be visited. + // defaults to clang_Location_isFromMainFile() + virtual bool visitLocation(const QString &fileName, LocationType locationType) const; + + virtual StartTokenResult startToken(const CXCursor &cursor) = 0; + virtual bool endToken(const CXCursor &cursor) = 0; + + StartTokenResult cbHandleStartToken(const CXCursor &cursor); + bool cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult); + + QString getFileName(CXFile file) { return m_fileCache.getFileName(file); } + std::string_view getCodeSnippet(const CXCursor &cursor); + QString getCodeSnippetString(const CXCursor &cursor); + + Diagnostics diagnostics() const; + void setDiagnostics(const Diagnostics &d); + void appendDiagnostic(const Diagnostic &d); + + // For usage by the parser + bool _handleVisitLocation( const CXSourceLocation &location); + +private: + SourceFileCache m_fileCache; + Diagnostics m_diagnostics; + CXFile m_currentCxFile{}; + bool m_visitCurrent = true; +}; + +bool parse(const QByteArrayList &clangArgs, + bool addCompilerSupportArguments, + LanguageLevel level, unsigned clangFlags, BaseVisitor &ctx); + +} // namespace clang + +#endif // !CLANGPARSER_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp new file mode 100644 index 000000000..1651e09ec --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp @@ -0,0 +1,320 @@ +// 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 "clangutils.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QHashFunctions> +#include <QtCore/QProcess> + +#include <string_view> + +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept +{ + return c1.kind == c2.kind + && c1.xdata == c2.xdata + && std::equal(c1.data, c1.data + sizeof(c1.data) / sizeof(c1.data[0]), c2.data); +} + +size_t qHash(const CXCursor &c, size_t seed) noexcept +{ + return qHashMulti(seed, c.kind, c.xdata, c.data[0], c.data[1], c.data[2]); +} + +bool operator==(const CXType &t1, const CXType &t2) noexcept +{ + return t1.kind == t2.kind && t1.data[0] == t2.data[0] + && t1.data[1] == t2.data[1]; +} + +size_t qHash(const CXType &ct, size_t seed) noexcept +{ + return qHashMulti(seed, ct.kind, ct.data[0], ct.data[1]); +} + +namespace clang { + +SourceLocation getExpansionLocation(const CXSourceLocation &location) +{ + SourceLocation result; + clang_getExpansionLocation(location, &result.file, &result.line, &result.column, &result.offset); + return result; +} + +QString getFileName(CXFile file) +{ + QString result; + const CXString cxFileName = clang_getFileName(file); + // Has been observed to be 0 for invalid locations + if (const char *cFileName = clang_getCString(cxFileName)) + result = QString::fromUtf8(cFileName); + clang_disposeString(cxFileName); + return result; +} + +SourceLocation getCursorLocation(const CXCursor &cursor) +{ + const CXSourceRange extent = clang_getCursorExtent(cursor); + return getExpansionLocation(clang_getRangeStart(extent)); +} + +CXString getFileNameFromLocation(const CXSourceLocation &location) +{ + CXFile file; + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &file, &line, &column, &offset); + return clang_getFileName(file); +} + +SourceRange getCursorRange(const CXCursor &cursor) +{ + const CXSourceRange extent = clang_getCursorExtent(cursor); + return std::make_pair(getExpansionLocation(clang_getRangeStart(extent)), + getExpansionLocation(clang_getRangeEnd(extent))); +} + +QString getCursorKindName(CXCursorKind cursorKind) +{ + CXString kindName = clang_getCursorKindSpelling(cursorKind); + const QString result = QString::fromUtf8(clang_getCString(kindName)); + clang_disposeString(kindName); + return result; +} + +QString getCursorSpelling(const CXCursor &cursor) +{ + CXString cursorSpelling = clang_getCursorSpelling(cursor); + const QString result = QString::fromUtf8(clang_getCString(cursorSpelling)); + clang_disposeString(cursorSpelling); + return result; +} + +QString getCursorDisplayName(const CXCursor &cursor) +{ + CXString displayName = clang_getCursorDisplayName(cursor); + const QString result = QString::fromUtf8(clang_getCString(displayName)); + clang_disposeString(displayName); + return result; +} + +static inline bool isBuiltinType(CXTypeKind kind) +{ + return kind >= CXType_FirstBuiltin && kind <= CXType_LastBuiltin; +} + +// Resolve elaborated types occurring with clang 16 +static CXType resolveElaboratedType(const CXType &type) +{ + if (!isBuiltinType(type.kind)) { + CXCursor decl = clang_getTypeDeclaration(type); + auto resolvedType = clang_getCursorType(decl); + if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind) + return resolvedType; + } + return type; +} + +// Resolve typedefs +static CXType resolveTypedef(const CXType &type) +{ + auto result = type; + while (result.kind == CXType_Typedef) { + auto decl = clang_getTypeDeclaration(result); + auto resolved = clang_getTypedefDeclUnderlyingType(decl); + if (resolved.kind == CXType_Invalid) + break; + result = resolved; + } + return result; +} + +// Fully resolve a type from elaborated & typedefs +CXType fullyResolveType(const CXType &type) +{ + return resolveTypedef(resolveElaboratedType(type)); +} + +QString getTypeName(const CXType &type) +{ + CXString typeSpelling = clang_getTypeSpelling(type); + const QString result = QString::fromUtf8(clang_getCString(typeSpelling)); + clang_disposeString(typeSpelling); + return result; +} + +// Quick check for "::Type" +bool hasScopeResolution(const CXType &type) +{ + CXString typeSpelling = clang_getTypeSpelling(type); + std::string_view spelling = clang_getCString(typeSpelling); + const bool result = spelling.compare(0, 2, "::") == 0 + || spelling.find(" ::") != std::string::npos; + clang_disposeString(typeSpelling); + return result; +} + +// Resolve elaborated types occurring with clang 16 +QString getResolvedTypeName(const CXType &type) +{ + return getTypeName(resolveElaboratedType(type)); +} + +Diagnostic::Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s) + : message(m), source(Other), severity(s) +{ + setLocation(getCursorLocation(c)); +} + +Diagnostic Diagnostic::fromCXDiagnostic(CXDiagnostic cd) +{ + Diagnostic result; + result.source = Clang; + CXString spelling = clang_getDiagnosticSpelling(cd); + result.message = QString::fromUtf8(clang_getCString(spelling)); + clang_disposeString(spelling); + result.severity = clang_getDiagnosticSeverity(cd); + result.setLocation(getExpansionLocation(clang_getDiagnosticLocation(cd))); + + CXDiagnosticSet childDiagnostics = clang_getChildDiagnostics(cd); + if (const unsigned childCount = clang_getNumDiagnosticsInSet(childDiagnostics)) { + result.childMessages.reserve(int(childCount)); + const unsigned format = clang_defaultDiagnosticDisplayOptions(); + for (unsigned i = 0; i < childCount; ++i) { + CXDiagnostic childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, i); + CXString cdm = clang_formatDiagnostic(childDiagnostic, format); + result.childMessages.append(QString::fromUtf8(clang_getCString(cdm))); + clang_disposeString(cdm); + clang_disposeDiagnostic(childDiagnostic); + } + } + + return result; +} + +void Diagnostic::setLocation(const SourceLocation &sourceLocation) +{ + file = getFileName(sourceLocation.file); + line = sourceLocation.line; + column = sourceLocation.column; + offset = sourceLocation.offset; +} + +QList<Diagnostic> getDiagnostics(CXTranslationUnit tu) +{ + QList<Diagnostic> result; + const unsigned count = clang_getNumDiagnostics(tu); + result.reserve(int(count)); + for (unsigned i = 0; i < count; ++i) { + const CXDiagnostic d = clang_getDiagnostic(tu, i); + result.append(Diagnostic::fromCXDiagnostic(d)); + clang_disposeDiagnostic(d); + } + return result; +} + +std::pair<qsizetype, qsizetype> + parseTemplateArgumentList(const QString &l, + const TemplateArgumentHandler &handler, + qsizetype from) +{ + const auto ltPos = l.indexOf(u'<', from); + if (ltPos == - 1) + return std::make_pair(-1, -1); + auto startPos = ltPos + 1; + int level = 1; + for (qsizetype p = startPos, end = l.size(); p < end; ) { + const char c = l.at(p).toLatin1(); + switch (c) { + case ',': + case '>': + handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed()); + ++p; + if (c == '>') { + if (--level == 0) + return std::make_pair(ltPos, p); + // Skip over next ',': "a<b<c,d>,e>" + for (; p < end && (l.at(p).isSpace() || l.at(p) == u','); ++p) {} + } + startPos = p; + break; + case '<': + handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed()); + ++level; + startPos = ++p; + break; + default: + ++p; + break; + } + } + return std::make_pair(-1, -1); +} + +CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds) +{ + CXDiagnosticSeverity result = CXDiagnostic_Ignored; + for (const Diagnostic& d : ds) { + if (d.severity > result) + result = d.severity; + } + return result; +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug s, const SourceLocation &l) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << QDir::toNativeSeparators(clang::getFileName(l.file)) << ':' << l.line; + if (l.column) + s << ':' << l.column; + return s; +} + +// Roughly follow g++ format: +// file.cpp:214:37: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] +QDebug operator<<(QDebug s, const Diagnostic &d) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << d.file << ':'<< d.line << ':' << d.column << ": "; + switch (d.severity) { + case CXDiagnostic_Ignored: + s << "ignored"; + break; + case CXDiagnostic_Note: + s << "note"; + break; + case CXDiagnostic_Warning: + s << "warning"; + break; + case CXDiagnostic_Error: + s << "error"; + break; + case CXDiagnostic_Fatal: + s << "fatal"; + break; + } + s << ": " << d.message; + + if (d.source != Diagnostic::Clang) + s << " [other]"; + + if (const auto childMessagesCount = d.childMessages.size()) { + s << '\n'; + for (qsizetype i = 0; i < childMessagesCount; ++i) + s << " " << d.childMessages.at(i) << '\n'; + } + + return s; +} + +#endif // QT_NO_DEBUG_STREAM + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h new file mode 100644 index 000000000..fbbf95f1b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h @@ -0,0 +1,108 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGUTILS_H +#define CLANGUTILS_H + +#include <clang-c/Index.h> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QtCompare> +#include <QtCore/QList> + +#include <functional> +#include <utility> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept; +size_t qHash(const CXCursor &c, size_t seed = 0) noexcept; + +bool operator==(const CXType &t1, const CXType &t2) noexcept; +size_t qHash(const CXType &ct, size_t seed = 0) noexcept; + +namespace clang { + +QString getCursorKindName(CXCursorKind cursorKind); +QString getCursorSpelling(const CXCursor &cursor); +QString getCursorDisplayName(const CXCursor &cursor); +QString getTypeName(const CXType &type); +bool hasScopeResolution(const CXType &type); +CXType fullyResolveType(const CXType &type); +QString getResolvedTypeName(const CXType &type); +inline QString getCursorTypeName(const CXCursor &cursor) + { return getTypeName(clang_getCursorType(cursor)); } +inline QString getCursorResultTypeName(const CXCursor &cursor) + { return getTypeName(clang_getCursorResultType(cursor)); } + +inline bool isCursorValid(const CXCursor &c) +{ + return c.kind < CXCursor_FirstInvalid || c.kind > CXCursor_LastInvalid; +} + +QString getFileName(CXFile file); // Uncached,see BaseVisitor for a cached version + +struct SourceLocation +{ + bool equals(const SourceLocation &rhs) const; + + CXFile file = nullptr; + unsigned line = 0; + unsigned column = 0; + unsigned offset = 0; + + friend constexpr bool comparesEqual(const SourceLocation &lhs, + const SourceLocation &rhs) noexcept + { + return lhs.file == rhs.file && lhs.offset == rhs.offset; + } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(SourceLocation) +}; + +SourceLocation getExpansionLocation(const CXSourceLocation &location); + +using SourceRange = std::pair<SourceLocation, SourceLocation>; + +SourceLocation getCursorLocation(const CXCursor &cursor); +CXString getFileNameFromLocation(const CXSourceLocation &location); +SourceRange getCursorRange(const CXCursor &cursor); + +struct Diagnostic { + enum Source { Clang, Other }; + + Diagnostic() = default; + // Clang + static Diagnostic fromCXDiagnostic(CXDiagnostic cd); + // Other + explicit Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s = CXDiagnostic_Warning); + void setLocation(const SourceLocation &); + + QString message; + QStringList childMessages; + QString file; + unsigned line = 0; + unsigned column = 0; + unsigned offset = 0; + Source source = Clang; + CXDiagnosticSeverity severity = CXDiagnostic_Warning; +}; + +QList<Diagnostic> getDiagnostics(CXTranslationUnit tu); +CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds); + +// Parse a template argument list "a<b<c,d>,e>" and invoke a handler +// with each match (level and string). Return begin and end of the list. +using TemplateArgumentHandler = std::function<void (int, QStringView)>; + +std::pair<qsizetype, qsizetype> + parseTemplateArgumentList(const QString &l, + const TemplateArgumentHandler &handler, + qsizetype from = 0); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const SourceLocation &); +QDebug operator<<(QDebug, const Diagnostic &); +#endif // QT_NO_DEBUG_STREAM +} // namespace clang + +#endif // CLANGUTILS_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp new file mode 100644 index 000000000..20224020b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp @@ -0,0 +1,456 @@ +// 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 "compilersupport.h" +#include "header_paths.h" +#include "clangutils.h" + +#include <reporthandler.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QProcess> +#include <QtCore/QStandardPaths> +#include <QtCore/QStringList> +#include <QtCore/QVersionNumber> + +#include <clang-c/Index.h> + +#include <algorithm> +#include <iterator> + +using namespace Qt::StringLiterals; + +namespace clang { + +QVersionNumber libClangVersion() +{ + return QVersionNumber(CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR); +} + +static Compiler _compiler = +#if defined (Q_CC_CLANG) + Compiler::Clang; +#elif defined (Q_CC_MSVC) + Compiler::Msvc; +#else + Compiler::Gpp; +#endif + +Compiler compiler() { return _compiler; } + +bool setCompiler(const QString &name) +{ + bool result = true; + if (name == u"msvc") + _compiler = Compiler::Msvc; + else if (name == u"g++") + _compiler = Compiler::Gpp; + else if (name == u"clang") + _compiler = Compiler::Clang; + else + result = false; + return result; +} + +QString _compilerPath; // Pre-defined compiler path (from command line) + +const QString &compilerPath() +{ + return _compilerPath; +} + +void setCompilerPath(const QString &name) +{ + _compilerPath = name; +} + +static Platform _platform = +#if defined (Q_OS_DARWIN) + Platform::macOS; +#elif defined (Q_OS_WIN) + Platform::Windows; +#else + Platform::Unix; +#endif + +Platform platform() { return _platform; } + +bool setPlatform(const QString &name) +{ + bool result = true; + if (name == u"windows") + _platform = Platform::Windows; + else if (name == u"darwin") + _platform = Platform::macOS; + else if (name == u"unix") + _platform = Platform::Unix; + else + result = false; + return result; +} + +// 3/2024: Use a recent MSVC2022 for libclang 18.X +static QByteArray msvcCompatVersion() +{ + return libClangVersion() >= QVersionNumber(0, 64) ? "19.39"_ba : "19.26"_ba; +} + +static bool runProcess(const QString &program, const QStringList &arguments, + QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr) +{ + QProcess process; + process.start(program, arguments, QProcess::ReadWrite); + if (!process.waitForStarted()) { + qCWarning(lcShiboken).noquote().nospace() << "Unable to start " + << process.program() << ": " << process.errorString(); + return false; + } + process.closeWriteChannel(); + const bool finished = process.waitForFinished(); + const QByteArray stdErr = process.readAllStandardError(); + if (stdErrIn) + *stdErrIn = stdErr; + if (stdOutIn) + *stdOutIn = process.readAllStandardOutput(); + + if (!finished) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " timed out: " << stdErr; + process.kill(); + return false; + } + + if (process.exitStatus() != QProcess::NormalExit) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " crashed: " << stdErr; + return false; + } + + if (process.exitCode() != 0) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " exited " + << process.exitCode() << ": " << stdErr; + return false; + } + + return true; +} + +static QByteArray frameworkPath() { return QByteArrayLiteral(" (framework directory)"); } + +static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths) +{ + QByteArray homebrewPrefix = qgetenv("HOMEBREW_OPT"); + + // If HOMEBREW_OPT is found we assume that the build is happening + // inside a brew environment, which means we need to filter out + // the -isystem flags added by the brew clang shim. This is needed + // because brew passes the Qt include paths as system include paths + // and because our parser ignores system headers, Qt classes won't + // be found and thus compilation errors will occur. + if (homebrewPrefix.isEmpty()) + return; + + qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix + << "Assuming homebrew build environment."; + + HeaderPaths::iterator it = headerPaths.begin(); + while (it != headerPaths.end()) { + if (it->path.startsWith(homebrewPrefix)) { + qCInfo(lcShiboken) << "Filtering out homebrew include path: " + << it->path; + it = headerPaths.erase(it); + } else { + ++it; + } + } +} + +// Determine g++'s internal include paths from the output of +// g++ -E -x c++ - -v </dev/null +// Output looks like: +// #include <...> search starts here: +// /usr/local/include +// /System/Library/Frameworks (framework directory) +// End of search list. +static HeaderPaths gppInternalIncludePaths(const QString &compiler) +{ + HeaderPaths result; + QStringList arguments{u"-E"_s, u"-x"_s, u"c++"_s, u"-"_s, u"-v"_s}; + QByteArray stdOut; + QByteArray stdErr; + if (!runProcess(compiler, arguments, &stdOut, &stdErr)) + return result; + const QByteArrayList stdErrLines = stdErr.split('\n'); + bool isIncludeDir = false; + + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) + qCInfo(lcShiboken()).noquote().nospace() + << "gppInternalIncludePaths:\n compiler: " << compiler + << "\n stdOut: " << stdOut + << "\n stdErr: " << stdErr; + + for (const QByteArray &line : stdErrLines) { + if (isIncludeDir) { + if (line.startsWith(QByteArrayLiteral("End of search list"))) { + isIncludeDir = false; + } else { + HeaderPath headerPath{line.trimmed(), HeaderType::System}; + if (headerPath.path.endsWith(frameworkPath())) { + headerPath.type = HeaderType::FrameworkSystem; + headerPath.path.truncate(headerPath.path.size() - frameworkPath().size()); + } + result.append(headerPath); + } + } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) { + isIncludeDir = true; + } + } + + if (platform() == Platform::macOS) + filterHomebrewHeaderPaths(result); + + return result; +} + +// Detect Vulkan as supported from Qt 5.10 by checking the environment variables. +QByteArrayList detectVulkan() +{ + static const char *vulkanVariables[] = {"VULKAN_SDK", "VK_SDK_PATH"}; + for (const char *vulkanVariable : vulkanVariables) { + if (qEnvironmentVariableIsSet(vulkanVariable)) { + const auto option = QByteArrayLiteral("-isystem") + + qgetenv(vulkanVariable) + + QByteArrayLiteral("/include"); + return {option}; + } + } + return {}; +} + +// For MSVC, we set the MS compatibility version and let Clang figure out its own +// options and include paths. +// For the others, we pass "-nostdinc" since libclang tries to add it's own system +// include paths, which together with the clang compiler paths causes some clash +// which causes std types not being found and construct -I/-F options from the +// include paths of the host compiler. + +static QByteArray noStandardIncludeOption() { return QByteArrayLiteral("-nostdinc"); } + +// The clang builtin includes directory is used to find the definitions for +// intrinsic functions and builtin types. It is necessary to use the clang +// includes to prevent redefinition errors. The default toolchain includes +// should be picked up automatically by clang without specifying +// them implicitly. + +// Besides g++/Linux, as of MSVC 19.28.29334, MSVC needs clang includes +// due to PYSIDE-1433, LLVM-47099 + +static bool needsClangBuiltinIncludes() +{ + return platform() != Platform::macOS; +} + +static QString queryLlvmConfigDir(const QString &arg) +{ + static const QString llvmConfig = QStandardPaths::findExecutable(u"llvm-config"_s); + if (llvmConfig.isEmpty()) + return {}; + QByteArray stdOut; + if (!runProcess(llvmConfig, QStringList{arg}, &stdOut)) + return {}; + const QString path = QFile::decodeName(stdOut.trimmed()); + if (!QFileInfo::exists(path)) { + qCWarning(lcShiboken, R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", + __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); + return {}; + } + return path; +} + +static QString findClangLibDir() +{ + for (const char *envVar : {"LLVM_INSTALL_DIR", "CLANG_INSTALL_DIR"}) { + if (qEnvironmentVariableIsSet(envVar)) { + const QString path = QFile::decodeName(qgetenv(envVar)) + u"/lib"_s; + if (QFileInfo::exists(path)) + return path; + qCWarning(lcShiboken, "%s: %s as pointed to by %s does not exist.", + __FUNCTION__, qPrintable(path), envVar); + } + } + return queryLlvmConfigDir(u"--libdir"_s); +} + +static QString findClangBuiltInIncludesDir() +{ + // Find the include directory of the highest version. + const QString clangPathLibDir = findClangLibDir(); + if (!clangPathLibDir.isEmpty()) { + QString candidate; + QString clangDirName = clangPathLibDir + u"/clang"_s; + // PYSIDE-2769: llvm-config --libdir may report /usr/lib64 on manylinux_2_28_x86_64 + // whereas the includes are under /usr/lib/clang/../include. + if (!QFileInfo::exists(clangDirName) && clangPathLibDir.endsWith("64"_L1)) { + const QString fallback = clangPathLibDir.sliced(0, clangPathLibDir.size() - 2); + clangDirName = fallback + u"/clang"_s; + qCWarning(lcShiboken, "%s: Falling back from %s to %s.", + __FUNCTION__, qPrintable(clangPathLibDir), qPrintable(fallback)); + } + + QVersionNumber lastVersionNumber(1, 0, 0); + QDir clangDir(clangDirName); + const QFileInfoList versionDirs = + clangDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + if (versionDirs.isEmpty()) + qCWarning(lcShiboken, "%s: No subdirectories found in %s.", + __FUNCTION__, qPrintable(clangDirName)); + for (const QFileInfo &fi : versionDirs) { + const QString fileName = fi.fileName(); + if (fileName.at(0).isDigit()) { + const QVersionNumber versionNumber = QVersionNumber::fromString(fileName); + if (!versionNumber.isNull() && versionNumber > lastVersionNumber) { + candidate = fi.absoluteFilePath(); + lastVersionNumber = versionNumber; + } + } + } + if (!candidate.isEmpty()) + return candidate + "/include"_L1; + } + return queryLlvmConfigDir(u"--includedir"_s); +} + +QString compilerFromCMake() +{ +#ifdef CMAKE_CXX_COMPILER + return QString::fromLocal8Bit(CMAKE_CXX_COMPILER); +#else + return {}; +#endif +} + +// Return a compiler suitable for determining the internal include paths +static QString compilerFromCMake(const QString &defaultCompiler) +{ + if (!compilerPath().isEmpty()) + return compilerPath(); + // Exclude macOS since cmakeCompiler returns the full path instead of the + // /usr/bin/clang shim, which results in the default SDK sysroot path + // missing (PYSIDE-1032) + if (platform() == Platform::macOS) + return defaultCompiler; + QString cmakeCompiler = compilerFromCMake(); + if (cmakeCompiler.isEmpty()) + return defaultCompiler; + QFileInfo fi(cmakeCompiler); + // Should be absolute by default, but a user may specify -DCMAKE_CXX_COMPILER=cl.exe + if (fi.isRelative()) + return cmakeCompiler; + if (fi.exists()) + return fi.absoluteFilePath(); + // The compiler may not exist in case something like icecream or + // a non-standard-path was used on the build machine. Check + // the executable. + cmakeCompiler = QStandardPaths::findExecutable(fi.fileName()); + return cmakeCompiler.isEmpty() ? defaultCompiler : cmakeCompiler; +} + +static void appendClangBuiltinIncludes(HeaderPaths *p) +{ + const QString clangBuiltinIncludesDir = + QDir::toNativeSeparators(findClangBuiltInIncludesDir()); + if (clangBuiltinIncludesDir.isEmpty()) { + qCWarning(lcShiboken, "Unable to locate Clang's built-in include directory " + "(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR " + " nor running llvm-config). This may lead to parse errors."); + } else { + qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s", + CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR, + qPrintable(clangBuiltinIncludesDir)); + p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir), + HeaderType::System}); + } +} + +// Returns clang options needed for emulating the host compiler +QByteArrayList emulatedCompilerOptions(LanguageLevel level) +{ + QByteArrayList result; + HeaderPaths headerPaths; + switch (compiler()) { + case Compiler::Msvc: + result.append("-fms-compatibility-version="_ba + msvcCompatVersion()); + if (level < LanguageLevel::Cpp20) + result.append("-fdelayed-template-parsing"_ba); + result.append(QByteArrayLiteral("-Wno-microsoft-enum-value")); + result.append("/Zc:__cplusplus"_ba); + // Fix yvals_core.h: STL1000: Unexpected compiler version, expected Clang 7 or newer (MSVC2017 update) + result.append(QByteArrayLiteral("-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH")); + if (needsClangBuiltinIncludes()) + appendClangBuiltinIncludes(&headerPaths); + break; + case Compiler::Clang: + headerPaths.append(gppInternalIncludePaths(compilerFromCMake(u"clang++"_s))); + result.append(noStandardIncludeOption()); + break; + case Compiler::Gpp: + if (needsClangBuiltinIncludes()) + appendClangBuiltinIncludes(&headerPaths); + + // Append the c++ include paths since Clang is unable to find + // <type_traits> etc (g++ 11.3). + const HeaderPaths gppPaths = gppInternalIncludePaths(compilerFromCMake(u"g++"_s)); + for (const HeaderPath &h : gppPaths) { + if (h.path.contains("c++") || h.path.contains("sysroot")) + headerPaths.append(h); + } + break; + } + + std::transform(headerPaths.cbegin(), headerPaths.cend(), + std::back_inserter(result), HeaderPath::includeOption); + return result; +} + +LanguageLevel emulatedCompilerLanguageLevel() +{ + return LanguageLevel::Cpp17; +} + +struct LanguageLevelMapping +{ + const char *option; + LanguageLevel level; +}; + +static const LanguageLevelMapping languageLevelMapping[] = +{ + {"c++11", LanguageLevel::Cpp11}, + {"c++14", LanguageLevel::Cpp14}, + {"c++17", LanguageLevel::Cpp17}, + {"c++20", LanguageLevel::Cpp20}, + {"c++1z", LanguageLevel::Cpp1Z} +}; + +const char *languageLevelOption(LanguageLevel l) +{ + for (const LanguageLevelMapping &m : languageLevelMapping) { + if (m.level == l) + return m.option; + } + return nullptr; +} + +LanguageLevel languageLevelFromOption(const char *o) +{ + for (const LanguageLevelMapping &m : languageLevelMapping) { + if (!strcmp(m.option, o)) + return m.level; + } + return LanguageLevel::Default; +} + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h new file mode 100644 index 000000000..f1d63b7c3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h @@ -0,0 +1,56 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include <QtCore/QByteArrayList> + +QT_FORWARD_DECLARE_CLASS(QVersionNumber) +QT_FORWARD_DECLARE_CLASS(QString) + +enum class LanguageLevel { + Default, + Cpp11, + Cpp14, + Cpp17, + Cpp20, + Cpp1Z +}; + +enum class Compiler { + Msvc, + Gpp, + Clang +}; + +enum class Platform { + Unix, + Windows, + macOS +}; + +namespace clang { +QVersionNumber libClangVersion(); + +QByteArrayList emulatedCompilerOptions(LanguageLevel level); +LanguageLevel emulatedCompilerLanguageLevel(); + +const char *languageLevelOption(LanguageLevel l); +LanguageLevel languageLevelFromOption(const char *); + +QByteArrayList detectVulkan(); + +Compiler compiler(); +bool setCompiler(const QString &name); + +QString compilerFromCMake(); + +const QString &compilerPath(); +void setCompilerPath(const QString &name); + +Platform platform(); +bool setPlatform(const QString &name); +} // namespace clang + +#endif // COMPILERSUPPORT_H |