File indexing completed on 2024-05-12 05:40:59

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 "range-loop-detach.h"
0011 #include "ClazyContext.h"
0012 #include "FixItUtils.h"
0013 #include "LoopUtils.h"
0014 #include "PreProcessorVisitor.h"
0015 #include "QtUtils.h"
0016 #include "StmtBodyRange.h"
0017 #include "TypeUtils.h"
0018 #include "Utils.h"
0019 
0020 #include <clang/AST/Decl.h>
0021 #include <clang/AST/DeclCXX.h>
0022 #include <clang/AST/Expr.h>
0023 #include <clang/AST/Stmt.h>
0024 #include <clang/AST/StmtCXX.h>
0025 #include <clang/AST/Type.h>
0026 #include <llvm/Support/Casting.h>
0027 
0028 class ClazyContext;
0029 
0030 using namespace clang;
0031 
0032 namespace clazy
0033 {
0034 /**
0035  * Returns true if we can prove the container doesn't detach.
0036  * Returns false otherwise, meaning that you can't conclude anything if false is returned.
0037  *
0038  * For true to be returned, all these conditions must verify:
0039  * - Container is a local variable
0040  * - It's not passed to any function
0041  * - It's not assigned to another variable
0042  */
0043 bool containerNeverDetaches(const clang::VarDecl *valDecl, StmtBodyRange bodyRange) // clazy:exclude=function-args-by-value
0044 {
0045     // This helps for bug 367485
0046 
0047     if (!valDecl) {
0048         return false;
0049     }
0050 
0051     const auto *const context = dyn_cast<FunctionDecl>(valDecl->getDeclContext());
0052     if (!context) {
0053         return false;
0054     }
0055 
0056     bodyRange.body = context->getBody();
0057     if (!bodyRange.body) {
0058         return false;
0059     }
0060 
0061     if (valDecl->hasInit()) {
0062         if (const auto *cleanupExpr = dyn_cast<clang::ExprWithCleanups>(valDecl->getInit())) {
0063             if (const auto *ce = dyn_cast<clang::CXXConstructExpr>(cleanupExpr->getSubExpr())) {
0064                 if (!ce->isListInitialization() && !ce->isStdInitListInitialization()) {
0065                     // When initing via copy or move ctor there's possible detachments.
0066                     return false;
0067                 }
0068             } else if (dyn_cast<clang::CXXBindTemporaryExpr>(cleanupExpr->getSubExpr())) {
0069                 return false;
0070             }
0071         }
0072     }
0073 
0074     // TODO1: Being passed to a function as const should be OK
0075     if (Utils::isPassedToFunction(bodyRange, valDecl, false)) {
0076         return false;
0077     }
0078 
0079     return true;
0080 }
0081 }
0082 
0083 RangeLoopDetach::RangeLoopDetach(const std::string &name, ClazyContext *context)
0084     : CheckBase(name, context, Option_CanIgnoreIncludes)
0085 {
0086     context->enablePreprocessorVisitor();
0087 }
0088 
0089 void RangeLoopDetach::VisitStmt(clang::Stmt *stmt)
0090 {
0091     if (auto *rangeLoop = dyn_cast<CXXForRangeStmt>(stmt)) {
0092         processForRangeLoop(rangeLoop);
0093     }
0094 }
0095 
0096 bool RangeLoopDetach::islvalue(Expr *exp, SourceLocation &endLoc)
0097 {
0098     if (isa<DeclRefExpr>(exp)) {
0099         endLoc = clazy::locForEndOfToken(&m_astContext, exp->getBeginLoc());
0100         return true;
0101     }
0102 
0103     if (auto *me = dyn_cast<MemberExpr>(exp)) {
0104         auto *decl = me->getMemberDecl();
0105         if (!decl || isa<FunctionDecl>(decl)) {
0106             return false;
0107         }
0108 
0109         endLoc = clazy::locForEndOfToken(&m_astContext, me->getMemberLoc());
0110         return true;
0111     }
0112 
0113     return false;
0114 }
0115 
0116 void RangeLoopDetach::processForRangeLoop(CXXForRangeStmt *rangeLoop)
0117 {
0118     Expr *containerExpr = rangeLoop->getRangeInit();
0119     if (!containerExpr) {
0120         return;
0121     }
0122 
0123     QualType qt = containerExpr->getType();
0124     const Type *t = qt.getTypePtrOrNull();
0125     if (!t || !t->isRecordType()) {
0126         return;
0127     }
0128 
0129     if (qt.isConstQualified()) { // const won't detach
0130         return;
0131     }
0132 
0133     auto loopVariableType = rangeLoop->getLoopVariable()->getType();
0134     if (!clazy::unrefQualType(loopVariableType).isConstQualified() && loopVariableType->isReferenceType()) {
0135         return;
0136     }
0137 
0138     CXXRecordDecl *record = t->getAsCXXRecordDecl();
0139     if (!clazy::isQtCOWIterableClass(Utils::rootBaseClass(record))) {
0140         return;
0141     }
0142 
0143     StmtBodyRange bodyRange(nullptr, &sm(), rangeLoop->getBeginLoc());
0144     if (clazy::containerNeverDetaches(clazy::containerDeclForLoop(rangeLoop), bodyRange)) {
0145         return;
0146     }
0147 
0148     std::vector<FixItHint> fixits;
0149 
0150     SourceLocation end;
0151     if (islvalue(containerExpr, /*by-ref*/ end)) {
0152         PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor;
0153         if (!preProcessorVisitor || preProcessorVisitor->qtVersion() >= 50700) { // qAsConst() was added to 5.7
0154             clang::SourceRange exprRange = containerExpr->getSourceRange();
0155             llvm::StringRef exprText = Lexer::getSourceText(CharSourceRange::getTokenRange(exprRange.getBegin(), exprRange.getEnd()), sm(), lo());
0156             std::string insertion = (lo().CPlusPlus17 ? "std::as_const(" : "qAsConst(") + exprText.str() + ")";
0157             fixits.push_back(clazy::createReplacement(exprRange, insertion));
0158         }
0159     }
0160 
0161     auto *typedefType = t->getAs<TypedefType>(); // Typedefs in internal Qt code, like QStringList should not be resolved
0162     const std::string name = typedefType ? typedefType->getDecl()->getNameAsString() : record->getNameAsString();
0163     emitWarning(rangeLoop->getBeginLoc(), "c++11 range-loop might detach Qt container (" + name + ')', fixits);
0164 }