src/checks/level0/qstring-arg.cpp (206 lines of code) (raw):

/* SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org> SPDX-License-Identifier: LGPL-2.0-or-later */ #include "qstring-arg.h" #include "ClazyContext.h" #include "HierarchyUtils.h" #include "PreProcessorVisitor.h" #include "StringUtils.h" #include "Utils.h" #include "clazy_stl.h" #include <clang/AST/Decl.h> #include <clang/AST/DeclCXX.h> #include <clang/AST/Expr.h> #include <clang/AST/ExprCXX.h> #include <clang/AST/Stmt.h> #include <clang/Basic/Diagnostic.h> #include <clang/Basic/LLVM.h> #include <clang/Basic/SourceLocation.h> #include <clang/Lex/Lexer.h> #include <llvm/ADT/StringRef.h> #include <llvm/Support/Casting.h> #include <vector> using namespace clang; QStringArg::QStringArg(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { m_filesToIgnore = {"qstring.h"}; context->enablePreprocessorVisitor(); } static std::string variableNameFromArg(Expr *arg) { std::vector<DeclRefExpr *> declRefs; clazy::getChilds<DeclRefExpr>(arg, declRefs); if (declRefs.size() == 1) { ValueDecl *decl = declRefs.at(0)->getDecl(); return decl ? decl->getNameAsString() : std::string(); } return {}; } static CXXMethodDecl *isArgMethod(FunctionDecl *func, const char *className) { if (!func) { return nullptr; } auto *method = dyn_cast<CXXMethodDecl>(func); if (!method || clazy::name(method) != "arg") { return nullptr; } const CXXRecordDecl *record = method->getParent(); if (!record || clazy::name(record) != className) { return nullptr; } return method; } static bool isArgFuncWithOnlyQString(CallExpr *callExpr) { if (!callExpr) { return false; } CXXMethodDecl *method = isArgMethod(callExpr->getDirectCallee(), "QString"); if (!method) { return false; } ParmVarDecl *secondParam = method->getParamDecl(1); if (clazy::classNameFor(secondParam) == "QString") { return true; } ParmVarDecl *firstParam = method->getParamDecl(0); if (clazy::classNameFor(firstParam) != "QString" && !clazy::startsWith(firstParam->getType().getAsString(), "const char &")) { return false; } // This is a arg(QString, int, QChar) call, it's good if the second parameter is a default param return isa<CXXDefaultArgExpr>(callExpr->getArg(1)); } bool QStringArg::checkMultiArgWarningCase(const std::vector<clang::CallExpr *> &calls) { if (calls.size() <= 1) { return false; // Nothing to do } std::string replacement; SourceLocation beginLoc; int argAggregated = 0; for (CallExpr *call : calls) { for (auto *arg : call->arguments()) { if (!isa<CXXDefaultArgExpr>(arg)) { ++argAggregated; } } if (argAggregated > 9) { // Relevant for Qt5 return false; } if (!beginLoc.isValid()) { beginLoc = call->getBeginLoc(); } std::string callArgs; for (auto *arg : call->arguments()) { if (!isa<CXXDefaultArgExpr>(arg)) { if (!callArgs.empty()) { callArgs += ", "; } callArgs += Lexer::getSourceText(CharSourceRange::getTokenRange(arg->getSourceRange()), sm(), lo()).str(); } } // The args for the chained calls have to be prepended instead of appended replacement = callArgs + (replacement.empty() ? "" : ", ") + replacement; } if (auto *subexprCall = clazy::getFirstChildOfType<MemberExpr>(calls.back())) { emitWarning(beginLoc, "Use multi-arg instead", {FixItHint::CreateReplacement(SourceRange(subexprCall->getEndLoc(), calls.at(0)->getEndLoc()), "arg(" + replacement + ")")}); return true; } return false; } void QStringArg::checkForMultiArgOpportunities(CXXMemberCallExpr *memberCall) { if (!isArgFuncWithOnlyQString(memberCall)) { return; } if (memberCall->getBeginLoc().isMacroID()) { auto macroName = Lexer::getImmediateMacroName(memberCall->getBeginLoc(), sm(), lo()); if (macroName == "QT_REQUIRE_VERSION") { // bug #391851 return; } } std::vector<clang::CallExpr *> callExprs = Utils::callListForChain(memberCall); std::vector<clang::CallExpr *> argCalls; for (auto *call : callExprs) { if (!clazy::contains(m_alreadyProcessedChainedCalls, call) && isArgFuncWithOnlyQString(call)) { argCalls.push_back(call); m_alreadyProcessedChainedCalls.push_back(call); } else { if (checkMultiArgWarningCase(argCalls)) { return; } argCalls.clear(); } } checkMultiArgWarningCase(argCalls); } bool QStringArg::checkQLatin1StringCase(CXXMemberCallExpr *memberCall) { const PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor; if (!preProcessorVisitor || preProcessorVisitor->qtVersion() < 51400) { // QLatin1String::arg() was introduced in Qt 5.14 return false; } if (!isArgMethod(memberCall->getDirectCallee(), "QLatin1String")) { return false; } if (memberCall->getNumArgs() == 0) { return false; } Expr *arg = memberCall->getArg(0); QualType t = arg->getType(); if (!t->isIntegerType() || t->isCharType()) { return false; } emitWarning(memberCall, "Argument passed to QLatin1String::arg() will be implicitly cast to QChar"); return true; } void QStringArg::VisitStmt(clang::Stmt *stmt) { auto *memberCall = dyn_cast<CXXMemberCallExpr>(stmt); if (!memberCall) { return; } if (shouldIgnoreFile(stmt->getBeginLoc())) { return; } checkForMultiArgOpportunities(memberCall); if (checkQLatin1StringCase(memberCall)) { return; } if (!isOptionSet("fillChar-overloads")) { return; } CXXMethodDecl *method = isArgMethod(memberCall->getDirectCallee(), "QString"); if (!method) { return; } if (clazy::simpleArgTypeName(method, method->getNumParams() - 1, lo()) == "QChar") { // The second arg wasn't passed, so this is a safe and unambiguous use, like .arg(1) if (isa<CXXDefaultArgExpr>(memberCall->getArg(1))) { return; } const ParmVarDecl *p = method->getParamDecl(2); if (p && clazy::name(p) == "base") { // User went through the trouble specifying a base, lets allow it if it's a literal. std::vector<IntegerLiteral *> literals; clazy::getChilds<IntegerLiteral>(memberCall->getArg(2), literals); if (!literals.empty()) { return; } std::string variableName = clazy::toLower(variableNameFromArg(memberCall->getArg(2))); if (clazy::contains(variableName, "base")) { return; } } p = method->getParamDecl(1); if (p && clazy::name(p) == "fieldWidth") { // He specified a literal, so he knows what he's doing, otherwise he would have put it directly in the string std::vector<IntegerLiteral *> literals; clazy::getChilds<IntegerLiteral>(memberCall->getArg(1), literals); if (!literals.empty()) { return; } // the variable is named "width", user knows what he's doing std::string variableName = clazy::toLower(variableNameFromArg(memberCall->getArg(1))); if (clazy::contains(variableName, "width")) { return; } } emitWarning(stmt->getBeginLoc(), "Using QString::arg() with fillChar overload"); } }