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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "container-anti-pattern.h"
0008 #include "HierarchyUtils.h"
0009 #include "LoopUtils.h"
0010 #include "StringUtils.h"
0011 #include "Utils.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/Stmt.h>
0019 #include <clang/Basic/LLVM.h>
0020 #include <llvm/Support/Casting.h>
0021 
0022 #include <vector>
0023 
0024 class ClazyContext;
0025 
0026 using namespace clang;
0027 
0028 ContainerAntiPattern::ContainerAntiPattern(const std::string &name, ClazyContext *context)
0029     : CheckBase(name, context, Option_CanIgnoreIncludes)
0030 {
0031 }
0032 
0033 static bool isInterestingCall(CallExpr *call)
0034 {
0035     FunctionDecl *func = call ? call->getDirectCallee() : nullptr;
0036     if (!func) {
0037         return false;
0038     }
0039 
0040     static const std::vector<std::string> methods = {
0041         "QVector::toList",
0042         "QList::toVector",
0043         "QMap::values",
0044         "QMap::keys",
0045         "QSet::toList",
0046         "QSet::values",
0047         "QHash::values",
0048         "QHash::keys",
0049         "QList::toList", // In case one has code compiling against Qt6, but Qt5 compat
0050     };
0051 
0052     return clazy::contains(methods, clazy::qualifiedMethodName(func));
0053 }
0054 
0055 void ContainerAntiPattern::VisitStmt(clang::Stmt *stmt)
0056 {
0057     if (handleLoop(stmt)) { // catch for (auto i : map.values()) and such
0058         return;
0059     }
0060 
0061     if (VisitQSet(stmt)) {
0062         return;
0063     }
0064 
0065     std::vector<CallExpr *> calls = Utils::callListForChain(dyn_cast<CallExpr>(stmt));
0066     if (calls.size() < 2) {
0067         return;
0068     }
0069 
0070     // For an expression like set.toList().count()...
0071     CallExpr *callexpr1 = calls[calls.size() - 1]; // ...this would be toList()
0072     // CallExpr *callexpr2 = calls[calls.size() - 2]; // ...and this would be count()
0073 
0074     if (!isInterestingCall(callexpr1)) {
0075         return;
0076     }
0077 
0078     emitWarning(stmt->getBeginLoc(), "allocating an unneeded temporary container");
0079 }
0080 
0081 bool ContainerAntiPattern::VisitQSet(Stmt *stmt)
0082 {
0083     auto *secondCall = dyn_cast<CXXMemberCallExpr>(stmt);
0084     if (!secondCall || !secondCall->getMethodDecl()) {
0085         return false;
0086     }
0087 
0088     CXXMethodDecl *secondMethod = secondCall->getMethodDecl();
0089     const std::string secondMethodName = clazy::qualifiedMethodName(secondMethod);
0090     if (secondMethodName != "QSet::isEmpty") {
0091         return false;
0092     }
0093 
0094     std::vector<CallExpr *> chainedCalls = Utils::callListForChain(secondCall);
0095     if (chainedCalls.size() < 2) {
0096         return false;
0097     }
0098 
0099     CallExpr *firstCall = chainedCalls[chainedCalls.size() - 1];
0100     FunctionDecl *firstFunc = firstCall->getDirectCallee();
0101     if (!firstFunc) {
0102         return false;
0103     }
0104 
0105     auto *firstMethod = dyn_cast<CXXMethodDecl>(firstFunc);
0106     if (!firstMethod || clazy::qualifiedMethodName(firstMethod) != "QSet::intersect") {
0107         return false;
0108     }
0109 
0110     emitWarning(stmt->getBeginLoc(), "Use QSet::intersects() instead");
0111     return true;
0112 }
0113 
0114 bool ContainerAntiPattern::handleLoop(Stmt *stm)
0115 {
0116     Expr *containerExpr = clazy::containerExprForLoop(stm);
0117     if (!containerExpr) {
0118         return false;
0119     }
0120 
0121     auto *memberExpr = clazy::getFirstChildOfType2<CXXMemberCallExpr>(containerExpr);
0122     if (isInterestingCall(memberExpr)) {
0123         emitWarning(stm->getBeginLoc(), "allocating an unneeded temporary container");
0124         return true;
0125     }
0126 
0127     return false;
0128 }