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

0001 /*
0002     SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com
0003     SPDX-FileContributor: Waqar Ahmed <waqar.ahmed@kdab.com>
0004     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0005     SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "use-static-qregularexpression.h"
0011 #include "HierarchyUtils.h"
0012 #include "QtUtils.h"
0013 #include "TypeUtils.h"
0014 #include "Utils.h"
0015 
0016 #include <clang/AST/AST.h>
0017 #include <clang/AST/ExprCXX.h>
0018 
0019 using namespace clang;
0020 
0021 UseStaticQRegularExpression::UseStaticQRegularExpression(const std::string &name, ClazyContext *context)
0022     : CheckBase(name, context)
0023 {
0024 }
0025 
0026 static MaterializeTemporaryExpr *isArgTemporaryObj(Expr *arg0)
0027 {
0028     return dyn_cast_or_null<MaterializeTemporaryExpr>(arg0);
0029 }
0030 
0031 static VarDecl *getVarDecl(Expr *arg)
0032 {
0033     auto *declRefExpr = dyn_cast<DeclRefExpr>(arg);
0034     declRefExpr = declRefExpr ? declRefExpr : clazy::getFirstChildOfType<DeclRefExpr>(arg);
0035     if (!declRefExpr) {
0036         return nullptr;
0037     }
0038     return dyn_cast_or_null<VarDecl>(declRefExpr->getDecl());
0039 }
0040 
0041 static Expr *getVarInitExpr(VarDecl *VDef)
0042 {
0043     return VDef->getDefinition() ? VDef->getDefinition()->getInit() : nullptr;
0044 }
0045 
0046 static bool isQStringModifiedAfterCreation(clang::Stmt *expr, LangOptions lo)
0047 {
0048     // This QString is the result of a .arg/.mid or similar call, check all args if they are a literal
0049     if (auto *methodCall = clazy::getFirstChildOfType<CXXMemberCallExpr>(expr)) {
0050         if (auto *callee = methodCall->getMethodDecl()) {
0051             if (callee->getReturnType().getAsString(lo) == "QString") {
0052                 return true;
0053             }
0054         }
0055     }
0056     return false;
0057 }
0058 
0059 static bool isQStringFromStringLiteral(Expr *qstring, LangOptions lo)
0060 {
0061     if (isArgTemporaryObj(qstring)) {
0062         // Is it compile time known QString i.e., not from a function call
0063         auto *qstringCtor = clazy::getFirstChildOfType<CXXConstructExpr>(qstring);
0064         if (!qstringCtor) {
0065             return false;
0066         }
0067 
0068         return clazy::getFirstChildOfType<StringLiteral>(qstringCtor);
0069     }
0070 
0071     if (auto *VD = getVarDecl(qstring)) {
0072         auto *stringLit = clazy::getFirstChildOfType<StringLiteral>(getVarInitExpr(VD));
0073         if (stringLit) {
0074             // If we have a string literal somewhere in there, but modify it using QString::arg or friends, we don't have a literal for th regex
0075             if (auto *constructExpr = clazy::getFirstChildOfType<CXXConstructExpr>(VD->getInit())) {
0076                 return !isQStringModifiedAfterCreation(constructExpr, lo);
0077             } else {
0078                 return true;
0079             }
0080         }
0081     }
0082     return false;
0083 }
0084 
0085 static bool isTemporaryQRegexObj(Expr *qregexVar, const LangOptions &lo)
0086 {
0087     // Get the QRegularExpression ctor
0088     auto *ctor = clazy::getFirstChildOfType<CXXConstructExpr>(qregexVar);
0089     if (!ctor || ctor->getNumArgs() == 0) {
0090         return false;
0091     }
0092 
0093     // Check if its first arg is "QString"
0094     auto *qstrArg = ctor->getArg(0);
0095     if (!qstrArg || clazy::typeName(qstrArg->getType(), lo, true) != "QString") {
0096         return false;
0097     }
0098 
0099     return isQStringFromStringLiteral(qstrArg, lo) && !isQStringModifiedAfterCreation(qstrArg, lo);
0100 }
0101 
0102 static bool isQRegexpFromStringLiteral(VarDecl *qregexVarDecl, LangOptions lo)
0103 {
0104     Expr *initExpr = getVarInitExpr(qregexVarDecl);
0105     if (!initExpr) {
0106         return false;
0107     }
0108 
0109     auto *ctorCall = dyn_cast<CXXConstructExpr>(initExpr);
0110     if (!ctorCall) {
0111         ctorCall = clazy::getFirstChildOfType<CXXConstructExpr>(initExpr);
0112         if (!ctorCall) {
0113             return false;
0114         }
0115     }
0116 
0117     if (ctorCall->getNumArgs() < 2) {
0118         return false;
0119     }
0120 
0121     auto *qstringArg = ctorCall->getArg(0);
0122     if (!qstringArg) {
0123         return false;
0124     }
0125 
0126     // For C++17, we have to put in more effort to resolving the initialization
0127     if (auto *expr = clazy::getFirstChildOfType<DeclRefExpr>(qstringArg)) {
0128         if (auto *exprClean = dyn_cast<VarDecl>(expr->getDecl())) {
0129             if (isQStringModifiedAfterCreation(exprClean->getInit(), lo)) {
0130                 return false;
0131             }
0132         }
0133     }
0134     return isQStringFromStringLiteral(qstringArg, lo) && !isQStringModifiedAfterCreation(qstringArg, lo);
0135 }
0136 
0137 static bool isArgNonStaticLocalVar(Expr *qregexp, LangOptions lo)
0138 {
0139     auto *varDecl = getVarDecl(qregexp);
0140     if (!varDecl) {
0141         return false;
0142     }
0143 
0144     if (!isQRegexpFromStringLiteral(varDecl, lo)) {
0145         return false;
0146     }
0147 
0148     return varDecl->isLocalVarDecl() && !varDecl->isStaticLocal();
0149 }
0150 
0151 static bool isOfAcceptableType(CXXMethodDecl *methodDecl)
0152 {
0153     const auto type = clazy::classNameFor(methodDecl);
0154     return type == "QString" || type == "QStringList" || type == "QRegularExpression" || type == "QListSpecialMethods" /* for QStringList in Qt6 */;
0155 }
0156 
0157 static bool firstArgIsQRegularExpression(CXXMethodDecl *methodDecl, const LangOptions &lo)
0158 {
0159     return clazy::simpleArgTypeName(methodDecl, 0, lo) == "QRegularExpression";
0160 }
0161 
0162 void UseStaticQRegularExpression::VisitStmt(clang::Stmt *stmt)
0163 {
0164     if (!stmt) {
0165         return;
0166     }
0167 
0168     auto *method = dyn_cast_or_null<CXXMemberCallExpr>(stmt);
0169     if (!method) {
0170         return;
0171     }
0172 
0173     if (method->getNumArgs() == 0) {
0174         return;
0175     }
0176 
0177     auto *methodDecl = method->getMethodDecl();
0178     if (!methodDecl || !methodDecl->getDeclName().isIdentifier()) {
0179         return;
0180     }
0181 
0182     if (!isOfAcceptableType(methodDecl)) {
0183         return;
0184     }
0185 
0186     // QRegularExpression.match()
0187     const auto methodName = methodDecl->getName();
0188     if (methodName == "match" || methodName == "globalMatch") {
0189         auto *obj = method->getImplicitObjectArgument()->IgnoreImpCasts();
0190         if (!obj) {
0191             return;
0192         }
0193 
0194         if (obj->isLValue()) {
0195             if (isArgNonStaticLocalVar(obj, lo())) {
0196                 emitWarning(obj->getBeginLoc(), "Don't create temporary QRegularExpression objects. Use a static QRegularExpression object instead");
0197                 return;
0198             }
0199         } else if (obj->isXValue()) {
0200             // is it a temporary?
0201             auto *temp = dyn_cast<MaterializeTemporaryExpr>(obj);
0202             if (!temp) {
0203                 return;
0204             }
0205             if (isTemporaryQRegexObj(temp, lo())) {
0206                 emitWarning(temp->getBeginLoc(), "Don't create temporary QRegularExpression objects. Use a static QRegularExpression object instead");
0207             }
0208         }
0209         return;
0210     }
0211 
0212     if (!firstArgIsQRegularExpression(methodDecl, lo())) {
0213         return;
0214     }
0215 
0216     Expr *qregexArg = method->getArg(0);
0217     if (!qregexArg) {
0218         return;
0219     }
0220 
0221     // Its a QString*().method(QRegularExpression(arg)) ?
0222     if (auto *temp = isArgTemporaryObj(qregexArg)) {
0223         if (isTemporaryQRegexObj(temp, lo())) {
0224             emitWarning(qregexArg->getBeginLoc(), "Don't create temporary QRegularExpression objects. Use a static QRegularExpression object instead");
0225         }
0226     }
0227 
0228     // Its a local QRegularExpression variable?
0229     if (isArgNonStaticLocalVar(qregexArg, lo())) {
0230         emitWarning(qregexArg->getBeginLoc(), "Don't create temporary QRegularExpression objects. Use a static QRegularExpression object instead");
0231     }
0232 }