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

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-ref.h"
0008 #include "ClazyContext.h"
0009 #include "FixItUtils.h"
0010 #include "HierarchyUtils.h"
0011 #include "StringUtils.h"
0012 #include "Utils.h"
0013 #include "clazy_stl.h"
0014 
0015 #include <clang/AST/DeclCXX.h>
0016 #include <clang/AST/Expr.h>
0017 #include <clang/AST/ExprCXX.h>
0018 #include <clang/AST/Stmt.h>
0019 #include <clang/Basic/Diagnostic.h>
0020 #include <clang/Basic/IdentifierTable.h>
0021 #include <clang/Basic/LLVM.h>
0022 #include <clang/Basic/SourceLocation.h>
0023 #include <clang/Lex/Lexer.h>
0024 #include <llvm/ADT/SmallVector.h>
0025 #include <llvm/ADT/StringRef.h>
0026 #include <llvm/Support/Casting.h>
0027 
0028 #include <array>
0029 #include <vector>
0030 
0031 namespace clang
0032 {
0033 class Decl;
0034 class LangOptions;
0035 } // namespace clang
0036 
0037 using namespace clang;
0038 
0039 StringRefCandidates::StringRefCandidates(const std::string &name, ClazyContext *context)
0040     : CheckBase(name, context, Option_CanIgnoreIncludes)
0041 {
0042 }
0043 
0044 static bool isInterestingFirstMethod(CXXMethodDecl *method)
0045 {
0046     if (!method || clazy::name(method->getParent()) != "QString") {
0047         return false;
0048     }
0049 
0050     static const llvm::SmallVector<StringRef, 3> list{"left", "mid", "right"};
0051     return clazy::contains(list, clazy::name(method));
0052 }
0053 
0054 static bool isInterestingSecondMethod(CXXMethodDecl *method, const clang::LangOptions &lo)
0055 {
0056     if (!method || clazy::name(method->getParent()) != "QString") {
0057         return false;
0058     }
0059 
0060     static const std::array<StringRef, 19> list{
0061         "compare", "contains", "count",   "startsWith", "endsWith", "indexOf", "isEmpty",     "isNull",   "lastIndexOf", "length",
0062         "size",    "toDouble", "toFloat", "toInt",      "toUInt",   "toULong", "toULongLong", "toUShort", "toUcs4",
0063     };
0064 
0065     if (!clazy::contains(list, clazy::name(method))) {
0066         return false;
0067     }
0068 
0069     return !clazy::anyArgIsOfAnySimpleType(method, {"QRegExp", "QRegularExpression"}, lo);
0070 }
0071 
0072 static bool isMethodReceivingQStringRef(CXXMethodDecl *method)
0073 {
0074     if (!method || clazy::name(method->getParent()) != "QString") {
0075         return false;
0076     }
0077 
0078     static const std::array<StringRef, 8> list{"append", "compare", "count", "indexOf", "endsWith", "lastIndexOf", "localAwareCompare", "startsWidth"};
0079 
0080     if (clazy::contains(list, clazy::name(method))) {
0081         return true;
0082     }
0083 
0084     if (method->getOverloadedOperator() == OO_PlusEqual) { // operator+=
0085         return true;
0086     }
0087 
0088     return false;
0089 }
0090 
0091 void StringRefCandidates::VisitStmt(clang::Stmt *stmt)
0092 {
0093     // Here we look for code like str.firstMethod().secondMethod(), where firstMethod() is for example mid() and secondMethod is for example, toInt()
0094 
0095     auto *call = dyn_cast<CallExpr>(stmt);
0096     if (!call || processCase1(dyn_cast<CXXMemberCallExpr>(call))) {
0097         return;
0098     }
0099 
0100     processCase2(call);
0101 }
0102 
0103 static bool containsChild(Stmt *s, Stmt *target)
0104 {
0105     if (!s) {
0106         return false;
0107     }
0108 
0109     if (s == target) {
0110         return true;
0111     }
0112 
0113     if (auto *mte = dyn_cast<MaterializeTemporaryExpr>(s)) {
0114         return containsChild(mte->getSubExpr(), target);
0115     } else if (auto *ice = dyn_cast<ImplicitCastExpr>(s)) {
0116         return containsChild(ice->getSubExpr(), target);
0117     } else if (auto *bte = dyn_cast<CXXBindTemporaryExpr>(s)) {
0118         return containsChild(bte->getSubExpr(), target);
0119     }
0120 
0121     return false;
0122 }
0123 
0124 bool StringRefCandidates::isConvertedToSomethingElse(clang::Stmt *s) const
0125 {
0126     // While passing a QString to the QVariant ctor works fine, passing QStringRef doesn't
0127     // So let's not warn when QStrings are cast to something else.
0128     if (!s) {
0129         return false;
0130     }
0131 
0132     auto *constr = clazy::getFirstParentOfType<CXXConstructExpr>(m_context->parentMap, s);
0133     if (!constr || constr->getNumArgs() == 0) {
0134         return false;
0135     }
0136 
0137     if (containsChild(constr->getArg(0), s)) {
0138         CXXConstructorDecl *ctor = constr->getConstructor();
0139         CXXRecordDecl *record = ctor ? ctor->getParent() : nullptr;
0140         return record ? record->getQualifiedNameAsString() != "QString" : false;
0141     }
0142 
0143     return false;
0144 }
0145 
0146 // Catches cases like: int i = s.mid(1, 1).toInt()
0147 bool StringRefCandidates::processCase1(CXXMemberCallExpr *memberCall)
0148 {
0149     if (!memberCall) {
0150         return false;
0151     }
0152 
0153     // In the AST secondMethod() is parent of firstMethod() call, and will be visited first (because at runtime firstMethod() is resolved first().
0154     // So check for interesting second method first
0155     CXXMethodDecl *method = memberCall->getMethodDecl();
0156     if (!isInterestingSecondMethod(method, lo())) {
0157         return false;
0158     }
0159 
0160     std::vector<CallExpr *> callExprs = Utils::callListForChain(memberCall);
0161     if (callExprs.size() < 2) {
0162         return false;
0163     }
0164 
0165     // The list now contains {secondMethod(), firstMethod() }
0166     auto *firstMemberCall = dyn_cast<CXXMemberCallExpr>(callExprs.at(1));
0167 
0168     if (!firstMemberCall || !isInterestingFirstMethod(firstMemberCall->getMethodDecl())) {
0169         return false;
0170     }
0171 
0172     if (isConvertedToSomethingElse(memberCall)) {
0173         return false;
0174     }
0175 
0176     const std::string firstMethodName = firstMemberCall->getMethodDecl()->getNameAsString();
0177     const std::vector<FixItHint> fixits = fixit(firstMemberCall);
0178 
0179     emitWarning(firstMemberCall->getEndLoc(), "Use " + firstMethodName + "Ref() instead", fixits);
0180     return true;
0181 }
0182 
0183 // Catches cases like: s.append(s2.mid(1, 1));
0184 bool StringRefCandidates::processCase2(CallExpr *call)
0185 {
0186     auto *memberCall = dyn_cast<CXXMemberCallExpr>(call);
0187     auto *operatorCall = memberCall ? nullptr : dyn_cast<CXXOperatorCallExpr>(call);
0188 
0189     CXXMethodDecl *method = nullptr;
0190     if (memberCall) {
0191         method = memberCall->getMethodDecl();
0192     } else if (operatorCall && operatorCall->getCalleeDecl()) {
0193         Decl *decl = operatorCall->getCalleeDecl();
0194         method = dyn_cast<CXXMethodDecl>(decl);
0195     }
0196 
0197     if (!isMethodReceivingQStringRef(method)) {
0198         return false;
0199     }
0200 
0201     Expr *firstArgument = call->getNumArgs() > 0 ? call->getArg(0) : nullptr;
0202     MaterializeTemporaryExpr *temp = firstArgument ? dyn_cast<MaterializeTemporaryExpr>(firstArgument) : nullptr;
0203     if (!temp) {
0204         Expr *secondArgument = call->getNumArgs() > 1 ? call->getArg(1) : nullptr;
0205         temp = secondArgument ? dyn_cast<MaterializeTemporaryExpr>(secondArgument) : nullptr;
0206         if (!temp) { // For the CXXOperatorCallExpr it's in the second argument
0207             return false;
0208         }
0209     }
0210 
0211     auto *innerCall = clazy::getFirstChildOfType2<CallExpr>(temp);
0212     auto *innerMemberCall = innerCall ? dyn_cast<CXXMemberCallExpr>(innerCall) : nullptr;
0213     if (!innerMemberCall) {
0214         return false;
0215     }
0216 
0217     CXXMethodDecl *innerMethod = innerMemberCall->getMethodDecl();
0218     if (!isInterestingFirstMethod(innerMethod)) {
0219         return false;
0220     }
0221 
0222     const std::vector<FixItHint> fixits = fixit(innerMemberCall);
0223 
0224     emitWarning(call->getBeginLoc(), "Use " + innerMethod->getNameAsString() + "Ref() instead", fixits);
0225     return true;
0226 }
0227 
0228 std::vector<FixItHint> StringRefCandidates::fixit(CXXMemberCallExpr *call)
0229 {
0230     auto *memberExpr = clazy::getFirstChildOfType<MemberExpr>(call);
0231     if (!memberExpr) {
0232         queueManualFixitWarning(call->getBeginLoc(), "Internal error 1");
0233         return {};
0234     }
0235 
0236     auto insertionLoc = Lexer::getLocForEndOfToken(memberExpr->getEndLoc(), 0, sm(), lo());
0237     // llvm::errs() << insertionLoc.printToString(sm()) << "\n";
0238     if (!insertionLoc.isValid()) {
0239         queueManualFixitWarning(call->getBeginLoc(), "Internal error 2");
0240         return {};
0241     }
0242 
0243     std::vector<FixItHint> fixits;
0244     fixits.push_back(clazy::createInsertion(insertionLoc, "Ref"));
0245     return fixits;
0246 }