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

0001 /*
0002     SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com
0003     SPDX-FileContributor: SĂ©rgio Martins <sergio.martins@kdab.com>
0004 
0005     SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "detaching-temporary.h"
0011 #include "QtUtils.h"
0012 #include "StringUtils.h"
0013 #include "Utils.h"
0014 #include "checkbase.h"
0015 #include "clazy_stl.h"
0016 
0017 #include <clang/AST/Decl.h>
0018 #include <clang/AST/DeclCXX.h>
0019 #include <clang/AST/Expr.h>
0020 #include <clang/AST/Stmt.h>
0021 #include <clang/AST/Type.h>
0022 #include <clang/Basic/LLVM.h>
0023 #include <llvm/Support/Casting.h>
0024 
0025 #include <unordered_map>
0026 #include <utility>
0027 
0028 class ClazyContext;
0029 
0030 using namespace clang;
0031 
0032 DetachingTemporary::DetachingTemporary(const std::string &name, ClazyContext *context)
0033     : DetachingBase(name, context, Option_CanIgnoreIncludes)
0034 {
0035     // Extra stuff that isn't really related to detachments but doesn't make sense to call on temporaries
0036     m_writeMethodsByType["QString"] = {"push_back", "push_front", "clear", "chop"};
0037     m_writeMethodsByType["QList"] = {"takeAt", "takeFirst", "takeLast", "removeOne", "removeAll", "erase"};
0038     m_writeMethodsByType["QVector"] = {"fill", "insert"};
0039     m_writeMethodsByType["QMap"] = {"erase", "insert", "insertMulti", "remove", "take"};
0040     m_writeMethodsByType["QHash"] = {"erase", "insert", "insertMulti", "remove", "take"};
0041     m_writeMethodsByType["QMultiHash"] = m_writeMethodsByType["QHash"];
0042     m_writeMethodsByType["QMultiMap"] = m_writeMethodsByType["QMap"];
0043     m_writeMethodsByType["QLinkedList"] = {"takeFirst", "takeLast", "removeOne", "removeAll", "erase"};
0044     m_writeMethodsByType["QSet"] = {"erase", "insert"};
0045     m_writeMethodsByType["QStack"] = {"push", "swap"};
0046     m_writeMethodsByType["QQueue"] = {"enqueue", "swap"};
0047     m_writeMethodsByType["QListSpecialMethods"] = {"sort", "replaceInStrings", "removeDuplicates"};
0048     m_writeMethodsByType["QStringList"] = m_writeMethodsByType["QListSpecialMethods"];
0049 }
0050 
0051 bool isAllowedChainedClass(const std::string &className)
0052 {
0053     static const std::vector<std::string> allowed = {"QString", "QByteArray", "QVariant"};
0054     return clazy::contains(allowed, className);
0055 }
0056 
0057 bool isAllowedChainedMethod(const std::string &methodName)
0058 {
0059     static const std::vector<std::string> allowed = {
0060         "QMap::keys",
0061         "QMap::values",
0062         "QHash::keys",
0063         "QMap::values",
0064         "QApplication::topLevelWidgets",
0065         "QAbstractItemView::selectedIndexes",
0066         "QListWidget::selectedItems",
0067         "QFile::encodeName",
0068         "QFile::decodeName",
0069         "QItemSelectionModel::selectedRows",
0070         "QTreeWidget::selectedItems",
0071         "QTableWidget::selectedItems",
0072         "QNetworkReply::rawHeaderList",
0073         "Mailbox::address",
0074         "QItemSelection::indexes",
0075         "QItemSelectionModel::selectedIndexes",
0076         "QMimeData::formats",
0077         "i18n",
0078         "QAbstractTransition::targetStates",
0079     };
0080     return clazy::contains(allowed, methodName);
0081 }
0082 
0083 void DetachingTemporary::VisitStmt(clang::Stmt *stm)
0084 {
0085     auto *callExpr = dyn_cast<CallExpr>(stm);
0086     if (!callExpr) {
0087         return;
0088     }
0089 
0090     // For a chain like getList().first(), returns {first(), getList()}
0091     std::vector<CallExpr *> callExprs = Utils::callListForChain(callExpr); // callExpr would be first()
0092     if (callExprs.size() < 2) {
0093         return;
0094     }
0095 
0096     CallExpr *firstCallToBeEvaluated = callExprs.at(callExprs.size() - 1); // This is the call to getList()
0097     FunctionDecl *firstFunc = firstCallToBeEvaluated->getDirectCallee();
0098     if (!firstFunc) {
0099         return;
0100     }
0101 
0102     QualType qt = firstFunc->getReturnType();
0103     const Type *firstFuncReturnType = qt.getTypePtrOrNull();
0104     if (!firstFuncReturnType) {
0105         return;
0106     }
0107 
0108     if (firstFuncReturnType->isReferenceType() || firstFuncReturnType->isPointerType()) {
0109         return;
0110     }
0111 
0112     if (qt.isConstQualified()) {
0113         return; // const doesn't detach
0114     }
0115 
0116     auto *firstMethod = dyn_cast<CXXMethodDecl>(firstFunc);
0117     if (isAllowedChainedMethod(clazy::qualifiedMethodName(firstFunc))) {
0118         return;
0119     }
0120 
0121     if (firstMethod && isAllowedChainedClass(firstMethod->getParent()->getNameAsString())) {
0122         return;
0123     }
0124 
0125     // Check if this is a QGlobalStatic
0126     if (firstMethod && clazy::name(firstMethod->getParent()) == "QGlobalStatic") {
0127         return;
0128     }
0129 
0130     CallExpr *secondCallToBeEvaluated = callExprs.at(callExprs.size() - 2); // This is the call to first()
0131     FunctionDecl *detachingFunc = secondCallToBeEvaluated->getDirectCallee();
0132     auto *detachingMethod = detachingFunc ? dyn_cast<CXXMethodDecl>(detachingFunc) : nullptr;
0133     const Type *detachingMethodReturnType = detachingMethod ? detachingMethod->getReturnType().getTypePtrOrNull() : nullptr;
0134     if (!detachingMethod || !detachingMethodReturnType) {
0135         return;
0136     }
0137 
0138     // Check if it's one of the implicit shared classes
0139     CXXRecordDecl *classDecl = detachingMethod->getParent();
0140     StringRef className = clazy::name(classDecl);
0141 
0142     const std::unordered_map<std::string, std::vector<StringRef>> &methodsByType = clazy::detachingMethods();
0143     auto it = methodsByType.find(static_cast<std::string>(className));
0144     auto it2 = m_writeMethodsByType.find(className);
0145 
0146     std::vector<StringRef> allowedFunctions;
0147     std::vector<StringRef> allowedWriteFunctions;
0148     if (it != methodsByType.end()) {
0149         allowedFunctions = it->second;
0150     }
0151 
0152     if (it2 != m_writeMethodsByType.end()) {
0153         allowedWriteFunctions = it2->second;
0154     }
0155 
0156     // Check if it's one of the detaching methods
0157     StringRef functionName = clazy::name(detachingMethod);
0158 
0159     std::string error;
0160 
0161     const bool isReadFunction = clazy::contains(allowedFunctions, functionName);
0162     const bool isWriteFunction = clazy::contains(allowedWriteFunctions, functionName);
0163 
0164     if (isReadFunction || isWriteFunction) {
0165         bool returnTypeIsIterator = false;
0166         CXXRecordDecl *returnRecord = detachingMethodReturnType->getAsCXXRecordDecl();
0167         if (returnRecord) {
0168             returnTypeIsIterator = clazy::name(returnRecord) == "iterator";
0169         }
0170 
0171         if (isWriteFunction && (detachingMethodReturnType->isVoidType() || returnTypeIsIterator)) {
0172             error = std::string("Modifying temporary container is pointless and it also detaches");
0173         } else {
0174             error = std::string("Don't call ") + clazy::qualifiedMethodName(detachingMethod) + std::string("() on temporary");
0175         }
0176     }
0177 
0178     if (!error.empty()) {
0179         emitWarning(stm->getBeginLoc(), error);
0180     }
0181 }
0182 
0183 bool DetachingTemporary::isDetachingMethod(CXXMethodDecl *method) const
0184 {
0185     if (!method) {
0186         return false;
0187     }
0188 
0189     CXXRecordDecl *record = method->getParent();
0190     if (!record) {
0191         return false;
0192     }
0193 
0194     if (DetachingBase::isDetachingMethod(method)) {
0195         return true;
0196     }
0197 
0198     StringRef className = clazy::name(record);
0199     if (auto it = m_writeMethodsByType.find(className); it != m_writeMethodsByType.cend()) {
0200         const auto &methods = it->second;
0201         if (clazy::contains(methods, clazy::name(method))) {
0202             return true;
0203         }
0204     }
0205 
0206     return false;
0207 }