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 }