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 }