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 }