src/checks/level0/strict-iterators.cpp (138 lines of code) (raw):

/* SPDX-FileCopyrightText: 2017 Sergio Martins <smartins@kde.org> SPDX-License-Identifier: LGPL-2.0-or-later */ #include "strict-iterators.h" #include "ClazyContext.h" #include "QtUtils.h" #include "StringUtils.h" #include "TypeUtils.h" #include "clazy_stl.h" #include <clang/AST/Decl.h> #include <clang/AST/DeclCXX.h> #include <clang/AST/Expr.h> #include <clang/AST/ExprCXX.h> #include <clang/AST/OperationKinds.h> #include <clang/AST/ParentMap.h> #include <clang/AST/Stmt.h> #include <clang/AST/Type.h> #include <clang/Basic/LLVM.h> #include <clang/Basic/SourceManager.h> #include <clang/Frontend/CompilerInstance.h> #include <llvm/ADT/StringRef.h> #include <llvm/Support/Casting.h> #include <assert.h> using namespace clang; static bool isMemberVariable(Expr *expr) { if (isa<MemberExpr>(expr)) { return true; } if (auto *ice = dyn_cast<ImplicitCastExpr>(expr)) { return isMemberVariable(ice->getSubExpr()); } return false; } // This got a bit messy since each Qt container produces a different AST, for example // QVector::iterator isn't even a class, it's a typedef. StrictIterators::StrictIterators(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void StrictIterators::VisitStmt(clang::Stmt *stmt) { if (handleOperator(dyn_cast<CXXOperatorCallExpr>(stmt))) { return; } // QVector's aren't actual classes, they are just typedefs to T* and const T* handleImplicitCast(dyn_cast<ImplicitCastExpr>(stmt)); } bool StrictIterators::handleImplicitCast(ImplicitCastExpr *implicitCast) { if (!implicitCast) { return false; } const std::string nameTo = clazy::simpleTypeName(implicitCast->getType(), m_context->ci.getLangOpts()); const QualType typeTo = implicitCast->getType(); CXXRecordDecl *recordTo = clazy::parentRecordForTypedef(typeTo); if (recordTo && !clazy::isQtCOWIterableClass(recordTo)) { return false; } recordTo = clazy::typeAsRecord(typeTo); if (recordTo && !clazy::isQtCOWIterator(recordTo)) { return false; } assert(implicitCast->getSubExpr()); if (isMemberVariable(implicitCast->getSubExpr())) { // Comparing a const_iterator against a member QVector<T>::iterator won't detach the container return false; } QualType typeFrom = implicitCast->getSubExpr()->getType(); CXXRecordDecl *recordFrom = clazy::parentRecordForTypedef(typeFrom); if (recordFrom && !clazy::isQtCOWIterableClass(recordFrom)) { return false; } // const_iterator might be a typedef to pointer, like const T *, instead of a class, so just check for const qualification in that case if (!(clazy::pointeeQualType(typeTo).isConstQualified() || clazy::endsWith(nameTo, "const_iterator"))) { return false; } // Allow conversions for mutating member functions of Qt container classes if (implicitCast->getCastKind() == CK_ConstructorConversion) { if (auto *memberCall = dyn_cast_or_null<CXXMemberCallExpr>(m_context->parentMap->getParent(implicitCast))) { auto memberFunctionDecl = memberCall->getMethodDecl(); if (auto *parentClass = memberFunctionDecl->getParent()) { static const std::vector<std::string> allow = { "QMap<>::insert", "QMap<>::erase", "QHash<>::erase", "QMultiHash<>::erase", "QList<>::emplace", "QList<>::erase", "QList<>::insert", "QVarLengthArray<>::emplace", "QVarLengthArray<>::erase", "QVarLengthArray<>::insert", "QSet<>::erase", "QSet<>::insert", "QMultiMap<>::erase", "QMultiMap<>::insert", }; const auto qualifiedName = parentClass->getNameAsString() + "<>::" + memberFunctionDecl->getNameAsString(); if (clazy::contains(allow, qualifiedName)) { return false; } } } emitWarning(implicitCast, "Mixing iterators with const_iterators"); return true; } // TODO: some util function to get the name of a nested class const bool nameToIsIterator = nameTo == "iterator" || clazy::endsWith(nameTo, "::iterator"); if (nameToIsIterator) { return false; } const std::string nameFrom = clazy::simpleTypeName(typeFrom, m_context->ci.getLangOpts()); const bool nameFromIsIterator = nameFrom == "iterator" || clazy::endsWith(nameFrom, "::iterator"); if (!nameFromIsIterator) { return false; } auto *p = m_context->parentMap->getParent(implicitCast); if (isa<CXXOperatorCallExpr>(p)) { return false; } emitWarning(implicitCast, "Mixing iterators with const_iterators"); return true; } bool StrictIterators::handleOperator(CXXOperatorCallExpr *op) { if (!op) { return false; } auto *method = dyn_cast_or_null<CXXMethodDecl>(op->getDirectCallee()); if (!method || method->getNumParams() != 1) { return false; } CXXRecordDecl *record = method->getParent(); if (!clazy::isQtCOWIterator(record)) { return false; } if (clazy::name(record) != "iterator") { return false; } ParmVarDecl *p = method->getParamDecl(0); const CXXRecordDecl *paramClass = p ? clazy::typeAsRecord(clazy::pointeeQualType(p->getType())) : nullptr; if (!paramClass || clazy::name(paramClass) != "const_iterator") { return false; } emitWarning(op, "Mixing iterators with const_iterators"); return true; }