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 "foreach.h" 0011 #include "ClazyContext.h" 0012 #include "HierarchyUtils.h" 0013 #include "PreProcessorVisitor.h" 0014 #include "QtUtils.h" 0015 #include "StringUtils.h" 0016 #include "TypeUtils.h" 0017 #include "Utils.h" 0018 #include "clazy_stl.h" 0019 0020 #include <clang/AST/Decl.h> 0021 #include <clang/AST/DeclCXX.h> 0022 #include <clang/AST/Expr.h> 0023 #include <clang/AST/ExprCXX.h> 0024 #include <clang/AST/Stmt.h> 0025 #include <clang/AST/Type.h> 0026 #include <clang/Basic/LLVM.h> 0027 #include <llvm/ADT/StringRef.h> 0028 #include <llvm/Support/Casting.h> 0029 0030 #include <unordered_map> 0031 #include <vector> 0032 0033 namespace clang 0034 { 0035 class Decl; 0036 class DeclContext; 0037 } // namespace clang 0038 0039 using namespace clang; 0040 0041 Foreach::Foreach(const std::string &name, ClazyContext *context) 0042 : CheckBase(name, context, Option_CanIgnoreIncludes) 0043 { 0044 context->enablePreprocessorVisitor(); 0045 } 0046 0047 void Foreach::VisitStmt(clang::Stmt *stmt) 0048 { 0049 PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor; 0050 if (!preProcessorVisitor || preProcessorVisitor->qtVersion() >= 50900) { 0051 // Disabled since 5.9 because the Q_FOREACH internals changed. 0052 // Not worth fixing it because range-loop is recommended 0053 return; 0054 } 0055 0056 auto *forStm = dyn_cast<ForStmt>(stmt); 0057 if (forStm) { 0058 m_lastForStmt = forStm; 0059 return; 0060 } 0061 0062 if (!m_lastForStmt) { 0063 return; 0064 } 0065 0066 auto *constructExpr = dyn_cast<CXXConstructExpr>(stmt); 0067 if (!constructExpr || constructExpr->getNumArgs() < 1) { 0068 return; 0069 } 0070 0071 CXXConstructorDecl *constructorDecl = constructExpr->getConstructor(); 0072 if (!constructorDecl || clazy::name(constructorDecl) != "QForeachContainer") { 0073 return; 0074 } 0075 0076 std::vector<DeclRefExpr *> declRefExprs; 0077 clazy::getChilds<DeclRefExpr>(constructExpr, declRefExprs); 0078 if (declRefExprs.empty()) { 0079 return; 0080 } 0081 0082 // Get the container value declaration 0083 DeclRefExpr *declRefExpr = declRefExprs.front(); 0084 auto *valueDecl = dyn_cast<ValueDecl>(declRefExpr->getDecl()); 0085 if (!valueDecl) { 0086 return; 0087 } 0088 0089 QualType containerQualType = constructExpr->getArg(0)->getType(); 0090 const Type *containerType = containerQualType.getTypePtrOrNull(); 0091 CXXRecordDecl *const containerRecord = containerType ? containerType->getAsCXXRecordDecl() : nullptr; 0092 0093 if (!containerRecord) { 0094 return; 0095 } 0096 0097 auto *rootBaseClass = Utils::rootBaseClass(containerRecord); 0098 StringRef containerClassName = clazy::name(rootBaseClass); 0099 const bool isQtContainer = clazy::isQtIterableClass(containerClassName); 0100 if (containerClassName.empty()) { 0101 emitWarning(stmt->getBeginLoc(), "internal error, couldn't get class name of foreach container, please report a bug"); 0102 return; 0103 } 0104 if (!isQtContainer) { 0105 emitWarning(stmt->getBeginLoc(), "foreach with STL container causes deep-copy (" + rootBaseClass->getQualifiedNameAsString() + ')'); 0106 return; 0107 } else if (containerClassName == "QVarLengthArray") { 0108 emitWarning(stmt->getBeginLoc(), "foreach with QVarLengthArray causes deep-copy"); 0109 return; 0110 } 0111 0112 checkBigTypeMissingRef(); 0113 0114 if (isa<MaterializeTemporaryExpr>(constructExpr->getArg(0))) { // Nothing else to check 0115 return; 0116 } 0117 0118 // const containers are fine 0119 if (valueDecl->getType().isConstQualified()) { 0120 return; 0121 } 0122 0123 // Now look inside the for statement for detachments 0124 if (containsDetachments(m_lastForStmt, valueDecl)) { 0125 emitWarning(stmt->getBeginLoc(), "foreach container detached"); 0126 } 0127 } 0128 0129 void Foreach::checkBigTypeMissingRef() 0130 { 0131 // Get the inner forstm 0132 std::vector<ForStmt *> forStatements; 0133 clazy::getChilds<ForStmt>(m_lastForStmt->getBody(), forStatements); 0134 if (forStatements.empty()) { 0135 return; 0136 } 0137 0138 // Get the variable declaration (lhs of foreach) 0139 std::vector<DeclStmt *> varDecls; 0140 clazy::getChilds<DeclStmt>(forStatements.at(0), varDecls); 0141 if (varDecls.empty()) { 0142 return; 0143 } 0144 0145 Decl *decl = varDecls.at(0)->getSingleDecl(); 0146 VarDecl *varDecl = decl ? dyn_cast<VarDecl>(decl) : nullptr; 0147 if (!varDecl) { 0148 return; 0149 } 0150 0151 clazy::QualTypeClassification classif; 0152 bool success = clazy::classifyQualType(m_context, varDecl->getType(), varDecl, /*by-ref*/ classif, forStatements.at(0)); 0153 if (!success) { 0154 return; 0155 } 0156 0157 if (classif.passBigTypeByConstRef || classif.passNonTriviallyCopyableByConstRef || classif.passSmallTrivialByValue) { 0158 std::string error; 0159 const std::string paramStr = varDecl->getType().getAsString(); 0160 if (classif.passBigTypeByConstRef) { 0161 error = "Missing reference in foreach with sizeof(T) = "; 0162 error += std::to_string(classif.size_of_T) + " bytes (" + paramStr + ')'; 0163 } else if (classif.passNonTriviallyCopyableByConstRef) { 0164 error = "Missing reference in foreach with non trivial type (" + paramStr + ')'; 0165 } else if (classif.passSmallTrivialByValue) { 0166 // error = "Pass small and trivially-copyable type by value (" + paramStr + ')'; 0167 // Don't warn. The compiler can (and most do) optimize this and generate the same code 0168 return; 0169 } 0170 0171 emitWarning(varDecl->getBeginLoc(), error); 0172 } 0173 } 0174 0175 bool Foreach::containsDetachments(Stmt *stm, clang::ValueDecl *containerValueDecl) 0176 { 0177 if (!stm) { 0178 return false; 0179 } 0180 0181 auto *memberExpr = dyn_cast<MemberExpr>(stm); 0182 if (memberExpr) { 0183 ValueDecl *valDecl = memberExpr->getMemberDecl(); 0184 if (valDecl && valDecl->isCXXClassMember()) { 0185 DeclContext *declContext = valDecl->getDeclContext(); 0186 auto *recordDecl = dyn_cast<CXXRecordDecl>(declContext); 0187 if (recordDecl) { 0188 const std::string className = Utils::rootBaseClass(recordDecl)->getQualifiedNameAsString(); 0189 const std::unordered_map<std::string, std::vector<StringRef>> &detachingMethodsMap = clazy::detachingMethods(); 0190 if (detachingMethodsMap.find(className) != detachingMethodsMap.end()) { 0191 const std::string functionName = valDecl->getNameAsString(); 0192 const auto &allowedFunctions = detachingMethodsMap.at(className); 0193 if (clazy::contains(allowedFunctions, functionName)) { 0194 Expr *expr = memberExpr->getBase(); 0195 0196 if (expr) { 0197 auto *refExpr = dyn_cast<DeclRefExpr>(expr); 0198 if (!refExpr) { 0199 auto *s = clazy::getFirstChildAtDepth(expr, 1); 0200 refExpr = dyn_cast<DeclRefExpr>(s); 0201 if (refExpr) { 0202 if (refExpr->getDecl() 0203 == containerValueDecl) { // Finally, check if this non-const member call is on the same container we're iterating 0204 return true; 0205 } 0206 } 0207 } 0208 } 0209 } 0210 } 0211 } 0212 } 0213 } 0214 0215 return clazy::any_of(stm->children(), [this, containerValueDecl](Stmt *child) { 0216 return this->containsDetachments(child, containerValueDecl); 0217 }); 0218 }