File indexing completed on 2024-05-12 05:40:57
0001 /* 0002 SPDX-FileCopyrightText: 2017 Sergio Martins <smartins@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "strict-iterators.h" 0008 #include "ClazyContext.h" 0009 #include "QtUtils.h" 0010 #include "StringUtils.h" 0011 #include "TypeUtils.h" 0012 #include "clazy_stl.h" 0013 0014 #include <clang/AST/Decl.h> 0015 #include <clang/AST/DeclCXX.h> 0016 #include <clang/AST/Expr.h> 0017 #include <clang/AST/ExprCXX.h> 0018 #include <clang/AST/OperationKinds.h> 0019 #include <clang/AST/ParentMap.h> 0020 #include <clang/AST/Stmt.h> 0021 #include <clang/AST/Type.h> 0022 #include <clang/Basic/LLVM.h> 0023 #include <clang/Basic/SourceManager.h> 0024 #include <clang/Frontend/CompilerInstance.h> 0025 #include <llvm/ADT/StringRef.h> 0026 #include <llvm/Support/Casting.h> 0027 0028 #include <assert.h> 0029 0030 using namespace clang; 0031 0032 static bool isMemberVariable(Expr *expr) 0033 { 0034 if (isa<MemberExpr>(expr)) { 0035 return true; 0036 } 0037 0038 if (auto *ice = dyn_cast<ImplicitCastExpr>(expr)) { 0039 return isMemberVariable(ice->getSubExpr()); 0040 } 0041 0042 return false; 0043 } 0044 0045 // This got a bit messy since each Qt container produces a different AST, for example 0046 // QVector::iterator isn't even a class, it's a typedef. 0047 0048 StrictIterators::StrictIterators(const std::string &name, ClazyContext *context) 0049 : CheckBase(name, context, Option_CanIgnoreIncludes) 0050 { 0051 } 0052 0053 void StrictIterators::VisitStmt(clang::Stmt *stmt) 0054 { 0055 if (handleOperator(dyn_cast<CXXOperatorCallExpr>(stmt))) { 0056 return; 0057 } 0058 0059 // QVector's aren't actual classes, they are just typedefs to T* and const T* 0060 handleImplicitCast(dyn_cast<ImplicitCastExpr>(stmt)); 0061 } 0062 0063 bool StrictIterators::handleImplicitCast(ImplicitCastExpr *implicitCast) 0064 { 0065 if (!implicitCast) { 0066 return false; 0067 } 0068 0069 const std::string nameTo = clazy::simpleTypeName(implicitCast->getType(), m_context->ci.getLangOpts()); 0070 0071 const QualType typeTo = implicitCast->getType(); 0072 CXXRecordDecl *recordTo = clazy::parentRecordForTypedef(typeTo); 0073 if (recordTo && !clazy::isQtCOWIterableClass(recordTo)) { 0074 return false; 0075 } 0076 0077 recordTo = clazy::typeAsRecord(typeTo); 0078 if (recordTo && !clazy::isQtCOWIterator(recordTo)) { 0079 return false; 0080 } 0081 0082 assert(implicitCast->getSubExpr()); 0083 0084 if (isMemberVariable(implicitCast->getSubExpr())) { 0085 // Comparing a const_iterator against a member QVector<T>::iterator won't detach the container 0086 return false; 0087 } 0088 0089 QualType typeFrom = implicitCast->getSubExpr()->getType(); 0090 CXXRecordDecl *recordFrom = clazy::parentRecordForTypedef(typeFrom); 0091 if (recordFrom && !clazy::isQtCOWIterableClass(recordFrom)) { 0092 return false; 0093 } 0094 0095 // const_iterator might be a typedef to pointer, like const T *, instead of a class, so just check for const qualification in that case 0096 if (!(clazy::pointeeQualType(typeTo).isConstQualified() || clazy::endsWith(nameTo, "const_iterator"))) { 0097 return false; 0098 } 0099 0100 // Allow conversions for mutating member functions of Qt container classes 0101 if (implicitCast->getCastKind() == CK_ConstructorConversion) { 0102 if (auto *memberCall = dyn_cast_or_null<CXXMemberCallExpr>(m_context->parentMap->getParent(implicitCast))) { 0103 auto memberFunctionDecl = memberCall->getMethodDecl(); 0104 if (auto *parentClass = memberFunctionDecl->getParent()) { 0105 static const std::vector<std::string> allow = { 0106 "QMap<>::insert", 0107 "QMap<>::erase", 0108 "QHash<>::erase", 0109 "QMultiHash<>::erase", 0110 "QList<>::emplace", 0111 "QList<>::erase", 0112 "QList<>::insert", 0113 "QVarLengthArray<>::emplace", 0114 "QVarLengthArray<>::erase", 0115 "QVarLengthArray<>::insert", 0116 "QSet<>::erase", 0117 "QSet<>::insert", 0118 "QMultiMap<>::erase", 0119 "QMultiMap<>::insert", 0120 }; 0121 0122 const auto qualifiedName = parentClass->getNameAsString() + "<>::" + memberFunctionDecl->getNameAsString(); 0123 if (clazy::contains(allow, qualifiedName)) { 0124 return false; 0125 } 0126 } 0127 } 0128 0129 emitWarning(implicitCast, "Mixing iterators with const_iterators"); 0130 return true; 0131 } 0132 0133 // TODO: some util function to get the name of a nested class 0134 const bool nameToIsIterator = nameTo == "iterator" || clazy::endsWith(nameTo, "::iterator"); 0135 if (nameToIsIterator) { 0136 return false; 0137 } 0138 0139 const std::string nameFrom = clazy::simpleTypeName(typeFrom, m_context->ci.getLangOpts()); 0140 const bool nameFromIsIterator = nameFrom == "iterator" || clazy::endsWith(nameFrom, "::iterator"); 0141 if (!nameFromIsIterator) { 0142 return false; 0143 } 0144 0145 auto *p = m_context->parentMap->getParent(implicitCast); 0146 if (isa<CXXOperatorCallExpr>(p)) { 0147 return false; 0148 } 0149 0150 emitWarning(implicitCast, "Mixing iterators with const_iterators"); 0151 0152 return true; 0153 } 0154 0155 bool StrictIterators::handleOperator(CXXOperatorCallExpr *op) 0156 { 0157 if (!op) { 0158 return false; 0159 } 0160 0161 auto *method = dyn_cast_or_null<CXXMethodDecl>(op->getDirectCallee()); 0162 if (!method || method->getNumParams() != 1) { 0163 return false; 0164 } 0165 0166 CXXRecordDecl *record = method->getParent(); 0167 if (!clazy::isQtCOWIterator(record)) { 0168 return false; 0169 } 0170 0171 if (clazy::name(record) != "iterator") { 0172 return false; 0173 } 0174 0175 ParmVarDecl *p = method->getParamDecl(0); 0176 CXXRecordDecl *paramClass = p ? clazy::typeAsRecord(clazy::pointeeQualType(p->getType())) : nullptr; 0177 if (!paramClass || clazy::name(paramClass) != "const_iterator") { 0178 return false; 0179 } 0180 0181 emitWarning(op, "Mixing iterators with const_iterators"); 0182 return true; 0183 }