File indexing completed on 2024-05-12 05:40:56

0001 /*
0002     SPDX-FileCopyrightText: 2018 Sergio Martins <smartins@kde.org>
0003     SPDX-FileCopyrightText: 2024 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "fully-qualified-moc-types.h"
0009 #include "AccessSpecifierManager.h"
0010 #include "ClazyContext.h"
0011 #include "HierarchyUtils.h"
0012 #include "StringUtils.h"
0013 #include "TypeUtils.h"
0014 
0015 #include <clang/AST/Decl.h>
0016 #include <clang/AST/DeclCXX.h>
0017 #include <clang/AST/Expr.h>
0018 #include <clang/AST/ExprCXX.h>
0019 #include <clang/AST/Stmt.h>
0020 #include <clang/AST/Type.h>
0021 #include <clang/Basic/IdentifierTable.h>
0022 #include <clang/Basic/LLVM.h>
0023 #include <clang/Basic/SourceManager.h>
0024 #include <clang/Lex/Token.h>
0025 #include <llvm/ADT/ArrayRef.h>
0026 #include <llvm/ADT/StringRef.h>
0027 #include <llvm/Support/Casting.h>
0028 
0029 namespace clang
0030 {
0031 class Decl;
0032 class MacroInfo;
0033 } // namespace clang
0034 
0035 using namespace clang;
0036 
0037 FullyQualifiedMocTypes::FullyQualifiedMocTypes(const std::string &name, ClazyContext *context)
0038     : CheckBase(name, context)
0039 {
0040     context->enableAccessSpecifierManager();
0041     enablePreProcessorCallbacks();
0042 }
0043 
0044 void FullyQualifiedMocTypes::VisitDecl(clang::Decl *decl)
0045 {
0046     auto *method = dyn_cast<CXXMethodDecl>(decl);
0047     if (!method) {
0048         return;
0049     }
0050 
0051     AccessSpecifierManager *accessSpecifierManager = m_context->accessSpecifierManager;
0052     if (!accessSpecifierManager) {
0053         return;
0054     }
0055 
0056     if (handleQ_PROPERTY(method)) {
0057         return;
0058     }
0059 
0060     if (method->isThisDeclarationADefinition() && !method->hasInlineBody()) {
0061         return;
0062     }
0063 
0064     QtAccessSpecifierType qst = accessSpecifierManager->qtAccessSpecifierType(method);
0065     if (qst != QtAccessSpecifier_Signal && qst != QtAccessSpecifier_Slot && qst != QtAccessSpecifier_Invokable) {
0066         return;
0067     }
0068 
0069     std::string qualifiedTypeName;
0070     std::string typeName;
0071     for (auto *param : method->parameters()) {
0072         QualType t = clazy::pointeeQualType(param->getType());
0073         if (!typeIsFullyQualified(t, /*by-ref*/ qualifiedTypeName, /*by-ref*/ typeName)) {
0074             SourceRange fixitRange = param->getTypeSourceInfo()->getTypeLoc().getSourceRange();
0075             // We don't want to include the & or * characters for the fixit range
0076             if (param->getType()->isReferenceType() || param->getType()->isPointerType()) {
0077                 fixitRange = SourceRange(fixitRange.getBegin(), fixitRange.getEnd().getLocWithOffset(-1));
0078             }
0079             std::vector fixits{FixItHint::CreateReplacement(fixitRange, qualifiedTypeName)};
0080             std::string warning = accessSpecifierManager->qtAccessSpecifierTypeStr(qst).str() + " arguments need to be fully-qualified";
0081             emitWarning(param->getTypeSpecStartLoc(), warning, fixits);
0082         }
0083     }
0084 
0085     if (qst == QtAccessSpecifier_Slot || qst == QtAccessSpecifier_Invokable) {
0086         QualType returnT = clazy::pointeeQualType(method->getReturnType());
0087         if (!typeIsFullyQualified(returnT, /*by-ref*/ qualifiedTypeName, /*by-ref*/ typeName)) {
0088             SourceRange returnTypeSourceRange = method->getReturnTypeSourceRange();
0089             // We don't want to include the & or * characters for the fixit range
0090             if (method->getReturnType()->isReferenceType() || method->getReturnType()->isPointerType()) {
0091                 returnTypeSourceRange = SourceRange(returnTypeSourceRange.getBegin(), returnTypeSourceRange.getEnd().getLocWithOffset(-1));
0092             }
0093             std::string warning = accessSpecifierManager->qtAccessSpecifierTypeStr(qst).str() + " return types need to be fully-qualified";
0094             std::vector fixits{FixItHint::CreateReplacement(returnTypeSourceRange, qualifiedTypeName)};
0095             emitWarning(returnTypeSourceRange.getBegin(), warning, fixits);
0096         }
0097     }
0098 }
0099 
0100 static std::string resolveTemplateType(const clang::TemplateSpecializationType *ptr, LangOptions lo, bool checkElabType = true);
0101 static std::string getQualifiedNameOfType(const Type *ptr, const LangOptions &lo, bool checkElabType = true)
0102 {
0103     if (auto *elabType = dyn_cast<ElaboratedType>(ptr); elabType && checkElabType) {
0104         if (auto *specType = dyn_cast<TemplateSpecializationType>(elabType->getNamedType().getTypePtrOrNull()); specType && !ptr->getAs<TypedefType>()) {
0105             return resolveTemplateType(specType, lo, false);
0106         }
0107     }
0108     if (auto *typedefDecl = ptr->getAs<TypedefType>(); typedefDecl && typedefDecl->getDecl()) {
0109         return typedefDecl->getDecl()->getQualifiedNameAsString();
0110     } else if (auto recordDecl = ptr->getAsRecordDecl()) {
0111         return recordDecl->getQualifiedNameAsString();
0112     }
0113     return QualType::getFromOpaquePtr(ptr).getAsString(lo);
0114 }
0115 
0116 // In Qt5, the type would contain lots of unneeded parameters: QDBusPendingReply<bool, void, void, void, void, void, void, void>
0117 // Thus we need to do all of the shenanigans below
0118 // specType->getCanonicalTypeInternal().getAsString(m_astContext.getPrintingPolicy())
0119 static std::string resolveTemplateType(const clang::TemplateSpecializationType *ptr, LangOptions lo, bool checkElabType)
0120 {
0121     std::string str = getQualifiedNameOfType(ptr, lo, checkElabType);
0122     str += "<";
0123     bool firstArg = true;
0124     for (auto arg : ptr->template_arguments()) { // We reconstruct the type with the explicitly specified template params
0125         if (!firstArg) {
0126             str += ", ";
0127         }
0128         firstArg = false;
0129         str += getQualifiedNameOfType(arg.getAsType().getTypePtr(), lo);
0130     }
0131     str += ">";
0132     return str;
0133 }
0134 
0135 bool FullyQualifiedMocTypes::typeIsFullyQualified(QualType t, std::string &qualifiedTypeName, std::string &typeName) const
0136 {
0137     qualifiedTypeName.clear();
0138     typeName.clear();
0139 
0140     if (auto *ptr = t.getTypePtrOrNull(); ptr && ptr->isRecordType()) {
0141         typeName = clazy::name(t.getUnqualifiedType(), lo(), /*asWritten=*/true); // Ignore qualifiers like const here
0142         if (typeName == "QPrivateSignal") {
0143             return true;
0144         }
0145 
0146         if (auto specType = ptr->getAs<TemplateSpecializationType>(); specType && !ptr->getAs<TypedefType>()) {
0147             qualifiedTypeName = resolveTemplateType(specType, lo());
0148         } else if (auto recordDecl = ptr->getAsRecordDecl(); recordDecl && recordDecl->isInAnonymousNamespace()) {
0149             return true;
0150         } else {
0151             qualifiedTypeName = getQualifiedNameOfType(ptr, lo());
0152         }
0153         return qualifiedTypeName.empty() || typeName == qualifiedTypeName;
0154     }
0155     return true;
0156 }
0157 
0158 bool FullyQualifiedMocTypes::isGadget(CXXRecordDecl *record) const
0159 {
0160     SourceLocation startLoc = record->getBeginLoc();
0161     for (const SourceLocation &loc : m_qgadgetMacroLocations) {
0162         if (sm().getFileID(loc) != sm().getFileID(startLoc)) {
0163             continue; // Different file
0164         }
0165 
0166         if (sm().isBeforeInSLocAddrSpace(startLoc, loc) && sm().isBeforeInSLocAddrSpace(loc, record->getEndLoc())) {
0167             return true; // We found a Q_GADGET after start and before end, it's ours.
0168         }
0169     }
0170     return false;
0171 }
0172 
0173 bool FullyQualifiedMocTypes::handleQ_PROPERTY(CXXMethodDecl *method)
0174 {
0175     if (clazy::name(method) != "qt_static_metacall" || !method->hasBody() || method->getDefinition() != method) {
0176         return false;
0177     }
0178     /**
0179      * Basically diffed a .moc file with and without a namespaced property,
0180      * the difference is one reinterpret_cast, under an if (_c == QMetaObject::ReadProperty), so
0181      * that's what we cheak.
0182      *
0183      * The AST doesn't have the Q_PROPERTIES as they expand to nothing, so we have
0184      * to do it this way.
0185      */
0186 
0187     auto ifs = clazy::getStatements<IfStmt>(method->getBody());
0188 
0189     for (auto *iff : ifs) {
0190         auto *bo = dyn_cast<BinaryOperator>(iff->getCond());
0191         if (!bo) {
0192             continue;
0193         }
0194 
0195         auto enumRefs = clazy::getStatements<DeclRefExpr>(bo->getRHS());
0196         if (enumRefs.size() == 1) {
0197             auto *enumerator = dyn_cast<EnumConstantDecl>(enumRefs.at(0)->getDecl());
0198             if (enumerator && clazy::name(enumerator) == "ReadProperty") {
0199                 auto switches = clazy::getStatements<SwitchStmt>(iff); // we only want the reinterpret_casts that are inside switches
0200                 for (auto *s : switches) {
0201                     auto reinterprets = clazy::getStatements<CXXReinterpretCastExpr>(s);
0202                     for (auto *reinterpret : reinterprets) {
0203                         QualType qt = clazy::pointeeQualType(reinterpret->getTypeAsWritten());
0204                         if (auto *record = qt->getAsCXXRecordDecl(); !record || !isGadget(record)) {
0205                             continue;
0206                         }
0207 
0208                         std::string nameAsWritten;
0209                         std::string qualifiedName;
0210                         if (!typeIsFullyQualified(qt, qualifiedName, nameAsWritten)) {
0211                             // warn in the cxxrecorddecl, since we don't want to warn in the .moc files.
0212                             // Ideally we would do some cross checking with the Q_PROPERTIES, but that's not in the AST
0213                             emitWarning(method->getParent()->getBeginLoc(),
0214                                         "Q_PROPERTY of type " + nameAsWritten + " should use full qualification (" + qualifiedName + ")");
0215                         }
0216                     }
0217                 }
0218                 return true;
0219             }
0220         }
0221     }
0222 
0223     return true; // true, so processing doesn't continue, it's a qt_static_metacall, nothing interesting here unless the properties above
0224 }
0225 
0226 void FullyQualifiedMocTypes::VisitMacroExpands(const clang::Token &MacroNameTok, const clang::SourceRange &range, const MacroInfo *)
0227 {
0228     IdentifierInfo *ii = MacroNameTok.getIdentifierInfo();
0229     if (ii && ii->getName() == "Q_GADGET") {
0230         registerQ_GADGET(range.getBegin());
0231     }
0232 }
0233 
0234 void FullyQualifiedMocTypes::registerQ_GADGET(SourceLocation loc)
0235 {
0236     m_qgadgetMacroLocations.push_back(loc);
0237 }