File indexing completed on 2024-05-12 05:41:03

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     SPDX-FileCopyrightText: 2015-2016 Sergio Martins <smartins@kde.org>
0005     SPDX-FileCopyrightText: 2024 Alexander Lohnau <alexander.lohnau@gmx.de>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "reserve-candidates.h"
0011 #include "ClazyContext.h"
0012 #include "ContextUtils.h"
0013 #include "HierarchyUtils.h"
0014 #include "LoopUtils.h"
0015 #include "MacroUtils.h"
0016 #include "QtUtils.h"
0017 #include "StringUtils.h"
0018 #include "Utils.h"
0019 #include "clazy_stl.h"
0020 
0021 #include <clang/AST/Decl.h>
0022 #include <clang/AST/DeclCXX.h>
0023 #include <clang/AST/Expr.h>
0024 #include <clang/AST/ExprCXX.h>
0025 #include <clang/AST/Stmt.h>
0026 #include <clang/AST/StmtCXX.h>
0027 #include <clang/AST/Type.h>
0028 #include <clang/Basic/LLVM.h>
0029 #include <clang/Basic/SourceLocation.h>
0030 #include <clang/Basic/SourceManager.h>
0031 #include <llvm/ADT/StringRef.h>
0032 #include <llvm/Support/Casting.h>
0033 
0034 #include <vector>
0035 
0036 using namespace clang;
0037 
0038 ReserveCandidates::ReserveCandidates(const std::string &name, ClazyContext *context)
0039     : CheckBase(name, context, Option_CanIgnoreIncludes)
0040 {
0041 }
0042 
0043 static bool paramIsSameTypeAs(const Type *paramType, CXXRecordDecl *classDecl)
0044 {
0045     if (!paramType || !classDecl) {
0046         return false;
0047     }
0048 
0049     if (paramType->getAsCXXRecordDecl() == classDecl) {
0050         return true;
0051     }
0052 
0053     const CXXRecordDecl *paramClassDecl = paramType->getPointeeCXXRecordDecl();
0054     return paramClassDecl && paramClassDecl == classDecl;
0055 }
0056 
0057 static bool isCandidateMethod(CXXMethodDecl *methodDecl)
0058 {
0059     if (!methodDecl) {
0060         return false;
0061     }
0062 
0063     CXXRecordDecl *classDecl = methodDecl->getParent();
0064     if (!classDecl) {
0065         return false;
0066     }
0067 
0068     if (!clazy::equalsAny(static_cast<std::string>(clazy::name(methodDecl)), {"append", "push_back", "push", "operator<<", "operator+="})) {
0069         return false;
0070     }
0071 
0072     if (!clazy::isAReserveClass(classDecl)) {
0073         return false;
0074     }
0075 
0076     // Catch cases like: QList<T>::append(const QList<T> &), which don't make sense to reserve.
0077     // In this case, the parameter has the same type of the class
0078     ParmVarDecl *parm = methodDecl->getParamDecl(0);
0079     return !paramIsSameTypeAs(parm->getType().getTypePtrOrNull(), classDecl);
0080 }
0081 
0082 static bool isCandidate(CallExpr *oper)
0083 {
0084     if (!oper) {
0085         return false;
0086     }
0087 
0088     return isCandidateMethod(dyn_cast_or_null<CXXMethodDecl>(oper->getDirectCallee()));
0089 }
0090 
0091 bool ReserveCandidates::containerWasReserved(clang::ValueDecl *valueDecl) const
0092 {
0093     return valueDecl && clazy::contains(m_foundReserves, valueDecl);
0094 }
0095 
0096 bool ReserveCandidates::acceptsValueDecl(ValueDecl *valueDecl) const
0097 {
0098     // Rules:
0099     // 1. The container variable must have been defined inside a function. Too many false positives otherwise.
0100     //      free to comment that out and go through the results, maybe you'll find something.
0101 
0102     // 2. If we found at least one reserve call, lets not warn about it.
0103 
0104     if (!valueDecl || isa<ParmVarDecl>(valueDecl) || containerWasReserved(valueDecl)) {
0105         return false;
0106     }
0107 
0108     if (clazy::isValueDeclInFunctionContext(valueDecl)) {
0109         return true;
0110     }
0111 
0112     // Actually, lets allow for some member variables containers if they are being used inside CTORs or DTORs
0113     // Those functions are only called once, so it's OK. For other member functions it's dangerous and needs
0114     // human inspection, if such member function would be called in a loop we would be constantly calling reserve
0115     // and in that case the built-in exponential growth is better.
0116 
0117     if (!m_context->lastMethodDecl || !(isa<CXXConstructorDecl>(m_context->lastMethodDecl) || isa<CXXDestructorDecl>(m_context->lastMethodDecl))) {
0118         return false;
0119     }
0120 
0121     CXXRecordDecl *record = Utils::isMemberVariable(valueDecl);
0122     return record && m_context->lastMethodDecl->getParent() == record;
0123 }
0124 
0125 bool ReserveCandidates::isReserveCandidate(ValueDecl *valueDecl, Stmt *loopBody, CallExpr *callExpr) const
0126 {
0127     if (!acceptsValueDecl(valueDecl)) {
0128         return false;
0129     }
0130 
0131     const bool isMemberVariable = Utils::isMemberVariable(valueDecl);
0132     // We only want containers defined outside of the loop we're examining
0133     if (!isMemberVariable && sm().isBeforeInSLocAddrSpace(loopBody->getBeginLoc(), valueDecl->getBeginLoc())) {
0134         return false;
0135     }
0136 
0137     if (isInComplexLoop(callExpr, valueDecl->getBeginLoc(), isMemberVariable)) {
0138         return false;
0139     }
0140 
0141     if (clazy::loopCanBeInterrupted(loopBody, m_context->sm, callExpr->getBeginLoc())) {
0142         return false;
0143     }
0144 
0145     return true;
0146 }
0147 
0148 void ReserveCandidates::VisitStmt(clang::Stmt *stm)
0149 {
0150     if (registerReserveStatement(stm)) {
0151         return;
0152     }
0153 
0154     auto *body = clazy::bodyFromLoop(stm);
0155     if (!body) {
0156         return;
0157     }
0158 
0159     const bool isForeach = clazy::isInMacro(&m_astContext, stm->getBeginLoc(), "Q_FOREACH");
0160 
0161     // If the body is another loop, we have nesting, ignore it now since the inner loops will be visited soon.
0162     if (isa<DoStmt>(body) || isa<WhileStmt>(body) || (!isForeach && isa<ForStmt>(body))) {
0163         return;
0164     }
0165 
0166     // TODO: Search in both branches of the if statement
0167     if (isa<IfStmt>(body)) {
0168         return;
0169     }
0170 
0171     // Get the list of member calls and operator<< that are direct childs of the loop statements
0172     // If it's inside an if statement we don't care.
0173     auto callExprs = clazy::getStatements<CallExpr>(body,
0174                                                     nullptr,
0175                                                     {},
0176                                                     /*depth=*/1,
0177                                                     /*includeParent=*/true,
0178                                                     clazy::IgnoreExprWithCleanups);
0179 
0180     for (CallExpr *callExpr : callExprs) {
0181         if (!isCandidate(callExpr)) {
0182             continue;
0183         }
0184 
0185         ValueDecl *valueDecl = Utils::valueDeclForCallExpr(callExpr);
0186         if (isReserveCandidate(valueDecl, body, callExpr)) {
0187             emitWarning(callExpr->getBeginLoc(), "Reserve candidate");
0188         }
0189     }
0190 }
0191 
0192 // Catch existing reserves
0193 bool ReserveCandidates::registerReserveStatement(Stmt *stm)
0194 {
0195     auto *memberCall = dyn_cast<CXXMemberCallExpr>(stm);
0196     if (!memberCall) {
0197         return false;
0198     }
0199 
0200     CXXMethodDecl *methodDecl = memberCall->getMethodDecl();
0201     if (!methodDecl || clazy::name(methodDecl) != "reserve") {
0202         return false;
0203     }
0204 
0205     CXXRecordDecl *decl = methodDecl->getParent();
0206     if (!clazy::isAReserveClass(decl)) {
0207         return false;
0208     }
0209 
0210     ValueDecl *valueDecl = Utils::valueDeclForMemberCall(memberCall);
0211     if (!valueDecl) {
0212         return false;
0213     }
0214 
0215     if (!clazy::contains(m_foundReserves, valueDecl)) {
0216         m_foundReserves.push_back(valueDecl);
0217     }
0218 
0219     return true;
0220 }
0221 
0222 bool ReserveCandidates::expressionIsComplex(clang::Expr *expr) const
0223 {
0224     if (!expr) {
0225         return false;
0226     }
0227 
0228     std::vector<CallExpr *> callExprs;
0229     clazy::getChilds<CallExpr>(expr, callExprs);
0230 
0231     for (CallExpr *callExpr : callExprs) {
0232         // In Qt5, this would have been a BinaryOperator. Ignore iterator unequality checks here
0233         if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(callExpr)) {
0234             std::string name = operatorCall->getDirectCallee()->getAsFunction()->getQualifiedNameAsString();
0235             if (clazy::contains(name, "iterator::operator")) {
0236                 continue;
0237             }
0238         }
0239 
0240         if (clazy::isJavaIterator(dyn_cast<CXXMemberCallExpr>(callExpr))) {
0241             continue;
0242         }
0243 
0244         QualType qt = callExpr->getType();
0245         const Type *t = qt.getTypePtrOrNull();
0246         if (t && (!t->isIntegerType() || t->isBooleanType())) {
0247             return true;
0248         }
0249     }
0250 
0251     std::vector<ArraySubscriptExpr *> subscriptExprs;
0252     clazy::getChilds<ArraySubscriptExpr>(expr, subscriptExprs);
0253     if (!subscriptExprs.empty()) {
0254         return true;
0255     }
0256 
0257     auto *binary = dyn_cast<BinaryOperator>(expr);
0258     if (binary && binary->isAssignmentOp()) { // Filter things like for ( ...; ...; next = node->next)
0259 
0260         Expr *rhs = binary->getRHS();
0261         if (isa<MemberExpr>(rhs) || (isa<ImplicitCastExpr>(rhs) && dyn_cast_or_null<MemberExpr>(clazy::getFirstChildAtDepth(rhs, 1)))) {
0262             return true;
0263         }
0264     }
0265 
0266     // llvm::errs() << expr->getStmtClassName() << "\n";
0267     return false;
0268 }
0269 
0270 bool ReserveCandidates::loopIsComplex(clang::Stmt *stm, bool &isLoop) const
0271 {
0272     isLoop = false;
0273 
0274     if (auto *forstm = dyn_cast<ForStmt>(stm)) {
0275         isLoop = true;
0276         return !forstm->getCond() || !forstm->getInc() || expressionIsComplex(forstm->getCond()) || expressionIsComplex(forstm->getInc());
0277     }
0278 
0279     if (isa<CXXForRangeStmt>(stm)) {
0280         isLoop = true;
0281         return false;
0282     }
0283 
0284     if (dyn_cast<DoStmt>(stm) || dyn_cast<WhileStmt>(stm)) {
0285         // Too many false-positives with while statements. Ignore it.
0286         isLoop = true;
0287         return true;
0288     }
0289 
0290     return false;
0291 }
0292 
0293 bool ReserveCandidates::isInComplexLoop(clang::Stmt *s, SourceLocation declLocation, bool isMemberVariable) const
0294 {
0295     if (!s || declLocation.isInvalid()) {
0296         return false;
0297     }
0298 
0299     int forCount = 0;
0300     int foreachCount = 0;
0301 
0302     static std::vector<unsigned int> nonComplexOnesCache;
0303     static std::vector<unsigned int> complexOnesCache;
0304     auto rawLoc = s->getBeginLoc().getRawEncoding();
0305 
0306     // For some reason we generate two warnings on some foreaches, so cache the ones we processed
0307     // and return true so we don't trigger a warning
0308     if (clazy::contains(nonComplexOnesCache, rawLoc) || clazy::contains(complexOnesCache, rawLoc)) {
0309         return true;
0310     }
0311 
0312     Stmt *parent = s;
0313     PresumedLoc lastForeachForStm;
0314     while ((parent = clazy::parent(m_context->parentMap, parent))) {
0315         const SourceLocation parentStart = parent->getBeginLoc();
0316         if (!isMemberVariable && sm().isBeforeInSLocAddrSpace(parentStart, declLocation)) {
0317             nonComplexOnesCache.push_back(rawLoc);
0318             return false;
0319         }
0320 
0321         bool isLoop = false;
0322         if (loopIsComplex(parent, isLoop)) {
0323             complexOnesCache.push_back(rawLoc);
0324             return true;
0325         }
0326 
0327         if (clazy::isInForeach(&m_astContext, parentStart)) {
0328             auto ploc = sm().getPresumedLoc(parentStart);
0329             if (Utils::presumedLocationsEqual(ploc, lastForeachForStm)) {
0330                 // Q_FOREACH comes in pairs, because each has two for statements inside, so ignore one when counting
0331             } else {
0332                 foreachCount++;
0333                 lastForeachForStm = ploc;
0334             }
0335         } else {
0336             if (isLoop) {
0337                 forCount++;
0338             }
0339         }
0340 
0341         if (foreachCount > 1 || forCount > 1) { // two foreaches are almost always a false-positve
0342             complexOnesCache.push_back(rawLoc);
0343             return true;
0344         }
0345     }
0346 
0347     nonComplexOnesCache.push_back(rawLoc);
0348     return false;
0349 }