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 }