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 }