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 }