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 }