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

0001 /*
0002     SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "qstring-arg.h"
0008 #include "ClazyContext.h"
0009 #include "HierarchyUtils.h"
0010 #include "PreProcessorVisitor.h"
0011 #include "StringUtils.h"
0012 #include "Utils.h"
0013 #include "clazy_stl.h"
0014 
0015 #include <clang/AST/Decl.h>
0016 #include <clang/AST/DeclCXX.h>
0017 #include <clang/AST/Expr.h>
0018 #include <clang/AST/ExprCXX.h>
0019 #include <clang/AST/Stmt.h>
0020 #include <clang/Basic/LLVM.h>
0021 #include <clang/Basic/SourceLocation.h>
0022 #include <clang/Lex/Lexer.h>
0023 #include <llvm/ADT/StringRef.h>
0024 #include <llvm/Support/Casting.h>
0025 
0026 #include <vector>
0027 
0028 class ClazyContext;
0029 
0030 using namespace clang;
0031 
0032 QStringArg::QStringArg(const std::string &name, ClazyContext *context)
0033     : CheckBase(name, context, Option_CanIgnoreIncludes)
0034 {
0035     m_filesToIgnore = {"qstring.h"};
0036     context->enablePreprocessorVisitor();
0037 }
0038 
0039 static std::string variableNameFromArg(Expr *arg)
0040 {
0041     std::vector<DeclRefExpr *> declRefs;
0042     clazy::getChilds<DeclRefExpr>(arg, declRefs);
0043     if (declRefs.size() == 1) {
0044         ValueDecl *decl = declRefs.at(0)->getDecl();
0045         return decl ? decl->getNameAsString() : std::string();
0046     }
0047 
0048     return {};
0049 }
0050 
0051 static CXXMethodDecl *isArgMethod(FunctionDecl *func, const char *className)
0052 {
0053     if (!func) {
0054         return nullptr;
0055     }
0056 
0057     auto *method = dyn_cast<CXXMethodDecl>(func);
0058     if (!method || clazy::name(method) != "arg") {
0059         return nullptr;
0060     }
0061 
0062     CXXRecordDecl *record = method->getParent();
0063     if (!record || clazy::name(record) != className) {
0064         return nullptr;
0065     }
0066 
0067     return method;
0068 }
0069 
0070 static bool isArgFuncWithOnlyQString(CallExpr *callExpr)
0071 {
0072     if (!callExpr) {
0073         return false;
0074     }
0075 
0076     CXXMethodDecl *method = isArgMethod(callExpr->getDirectCallee(), "QString");
0077     if (!method) {
0078         return false;
0079     }
0080 
0081     ParmVarDecl *secondParam = method->getParamDecl(1);
0082     if (clazy::classNameFor(secondParam) == "QString") {
0083         return true;
0084     }
0085 
0086     ParmVarDecl *firstParam = method->getParamDecl(0);
0087     if (clazy::classNameFor(firstParam) != "QString") {
0088         return false;
0089     }
0090 
0091     // This is a arg(QString, int, QChar) call, it's good if the second parameter is a default param
0092     return isa<CXXDefaultArgExpr>(callExpr->getArg(1));
0093 }
0094 
0095 bool QStringArg::checkMultiArgWarningCase(const std::vector<clang::CallExpr *> &calls)
0096 {
0097     const int size = calls.size();
0098     for (int i = 1; i < size; ++i) {
0099         auto *call = calls.at(i);
0100         if (calls.at(i - 1)->getNumArgs() + call->getNumArgs() <= 9) {
0101             emitWarning(call->getEndLoc(), "Use multi-arg instead");
0102             return true;
0103         }
0104     }
0105 
0106     return false;
0107 }
0108 
0109 void QStringArg::checkForMultiArgOpportunities(CXXMemberCallExpr *memberCall)
0110 {
0111     if (!isArgFuncWithOnlyQString(memberCall)) {
0112         return;
0113     }
0114 
0115     if (memberCall->getBeginLoc().isMacroID()) {
0116         auto macroName = Lexer::getImmediateMacroName(memberCall->getBeginLoc(), sm(), lo());
0117         if (macroName == "QT_REQUIRE_VERSION") { // bug #391851
0118             return;
0119         }
0120     }
0121 
0122     std::vector<clang::CallExpr *> callExprs = Utils::callListForChain(memberCall);
0123     std::vector<clang::CallExpr *> argCalls;
0124     for (auto *call : callExprs) {
0125         if (!clazy::contains(m_alreadyProcessedChainedCalls, call) && isArgFuncWithOnlyQString(call)) {
0126             argCalls.push_back(call);
0127             m_alreadyProcessedChainedCalls.push_back(call);
0128         } else {
0129             if (checkMultiArgWarningCase(argCalls)) {
0130                 return;
0131             }
0132             argCalls.clear();
0133         }
0134     }
0135 
0136     checkMultiArgWarningCase(argCalls);
0137 }
0138 
0139 bool QStringArg::checkQLatin1StringCase(CXXMemberCallExpr *memberCall)
0140 {
0141     PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor;
0142     if (!preProcessorVisitor || preProcessorVisitor->qtVersion() < 51400) {
0143         // QLatin1String::arg() was introduced in Qt 5.14
0144         return false;
0145     }
0146 
0147     if (!isArgMethod(memberCall->getDirectCallee(), "QLatin1String")) {
0148         return false;
0149     }
0150 
0151     if (memberCall->getNumArgs() == 0) {
0152         return false;
0153     }
0154 
0155     Expr *arg = memberCall->getArg(0);
0156     QualType t = arg->getType();
0157     if (!t->isIntegerType() || t->isCharType()) {
0158         return false;
0159     }
0160 
0161     emitWarning(memberCall, "Argument passed to QLatin1String::arg() will be implicitly cast to QChar");
0162     return true;
0163 }
0164 
0165 void QStringArg::VisitStmt(clang::Stmt *stmt)
0166 {
0167     auto *memberCall = dyn_cast<CXXMemberCallExpr>(stmt);
0168     if (!memberCall) {
0169         return;
0170     }
0171 
0172     if (shouldIgnoreFile(stmt->getBeginLoc())) {
0173         return;
0174     }
0175 
0176     checkForMultiArgOpportunities(memberCall);
0177 
0178     if (checkQLatin1StringCase(memberCall)) {
0179         return;
0180     }
0181 
0182     if (!isOptionSet("fillChar-overloads")) {
0183         return;
0184     }
0185 
0186     CXXMethodDecl *method = isArgMethod(memberCall->getDirectCallee(), "QString");
0187     if (!method) {
0188         return;
0189     }
0190 
0191     if (clazy::simpleArgTypeName(method, method->getNumParams() - 1, lo()) == "QChar") {
0192         // The second arg wasn't passed, so this is a safe and unambiguous use, like .arg(1)
0193         if (isa<CXXDefaultArgExpr>(memberCall->getArg(1))) {
0194             return;
0195         }
0196 
0197         ParmVarDecl *p = method->getParamDecl(2);
0198         if (p && clazy::name(p) == "base") {
0199             // User went through the trouble specifying a base, lets allow it if it's a literal.
0200             std::vector<IntegerLiteral *> literals;
0201             clazy::getChilds<IntegerLiteral>(memberCall->getArg(2), literals);
0202             if (!literals.empty()) {
0203                 return;
0204             }
0205 
0206             std::string variableName = clazy::toLower(variableNameFromArg(memberCall->getArg(2)));
0207             if (clazy::contains(variableName, "base")) {
0208                 return;
0209             }
0210         }
0211 
0212         p = method->getParamDecl(1);
0213         if (p && clazy::name(p) == "fieldWidth") {
0214             // He specified a literal, so he knows what he's doing, otherwise he would have put it directly in the string
0215             std::vector<IntegerLiteral *> literals;
0216             clazy::getChilds<IntegerLiteral>(memberCall->getArg(1), literals);
0217             if (!literals.empty()) {
0218                 return;
0219             }
0220 
0221             // the variable is named "width", user knows what he's doing
0222             std::string variableName = clazy::toLower(variableNameFromArg(memberCall->getArg(1)));
0223             if (clazy::contains(variableName, "width")) {
0224                 return;
0225             }
0226         }
0227 
0228         emitWarning(stmt->getBeginLoc(), "Using QString::arg() with fillChar overload");
0229     }
0230 }