File indexing completed on 2024-05-12 05:41:00
0001 /* 0002 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com 0003 SPDX-FileContributor: Sérgio Martins <sergio.martins@kdab.com> 0004 0005 SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "qstring-allocations.h" 0011 #include "ClazyContext.h" 0012 #include "FixItUtils.h" 0013 #include "HierarchyUtils.h" 0014 #include "QtUtils.h" 0015 #include "StringUtils.h" 0016 #include "Utils.h" 0017 #include "clazy_stl.h" 0018 0019 #include <clang/AST/Decl.h> 0020 #include <clang/AST/DeclCXX.h> 0021 #include <clang/AST/Expr.h> 0022 #include <clang/AST/ExprCXX.h> 0023 #include <clang/AST/Stmt.h> 0024 #include <clang/AST/StmtIterator.h> 0025 #include <clang/Basic/Diagnostic.h> 0026 #include <clang/Basic/LLVM.h> 0027 #include <clang/Basic/SourceLocation.h> 0028 #include <clang/Frontend/CompilerInstance.h> 0029 #include <clang/Lex/Lexer.h> 0030 #include <llvm/ADT/StringRef.h> 0031 #include <llvm/Support/Casting.h> 0032 #include <llvm/Support/raw_ostream.h> 0033 0034 #include <assert.h> 0035 0036 #include <utility> 0037 0038 namespace clang 0039 { 0040 class LangOptions; 0041 class ParentMap; 0042 class SourceManager; 0043 } // namespace clang 0044 0045 using namespace clang; 0046 0047 inline bool hasCharPtrArgument(clang::FunctionDecl *func, int expected_arguments = -1) 0048 { 0049 if (expected_arguments != -1 && (int)func->param_size() != expected_arguments) { 0050 return false; 0051 } 0052 0053 for (auto *param : Utils::functionParameters(func)) { 0054 clang::QualType qt = param->getType(); 0055 const clang::Type *t = qt.getTypePtrOrNull(); 0056 if (!t) { 0057 continue; 0058 } 0059 0060 if (const clang::Type *realT = t->getPointeeType().getTypePtrOrNull(); realT && realT->isCharType()) { 0061 return true; 0062 } 0063 } 0064 0065 return false; 0066 } 0067 0068 enum Fixit { 0069 FixitNone = 0, 0070 QLatin1StringAllocations = 0x1, 0071 FromLatin1_FromUtf8Allocations = 0x2, 0072 CharPtrAllocations = 0x4, 0073 }; 0074 0075 struct Latin1Expr { 0076 CXXConstructExpr *qlatin1ctorexpr; 0077 bool enableFixit; 0078 bool isValid() const 0079 { 0080 return qlatin1ctorexpr != nullptr; 0081 } 0082 }; 0083 0084 QStringAllocations::QStringAllocations(const std::string &name, ClazyContext *context) 0085 : CheckBase(name, context, Option_CanIgnoreIncludes) 0086 { 0087 } 0088 0089 void QStringAllocations::VisitStmt(clang::Stmt *stm) 0090 { 0091 if (m_context->isQtDeveloper() && clazy::isBootstrapping(m_context->ci.getPreprocessorOpts())) { 0092 // During bootstrap many QString::fromLatin1() are used instead of tr(), which causes 0093 // much noise 0094 return; 0095 } 0096 0097 VisitCtor(stm); 0098 VisitOperatorCall(stm); 0099 VisitFromLatin1OrUtf8(stm); 0100 VisitAssignOperatorQLatin1String(stm); 0101 } 0102 0103 static bool betterTakeQLatin1String(CXXMethodDecl *method, StringLiteral *lt) 0104 { 0105 static const std::vector<StringRef> methods = 0106 {"append", "compare", "endsWith", "startsWith", "insert", "lastIndexOf", "prepend", "replace", "contains", "indexOf"}; 0107 0108 if (!clazy::isOfClass(method, "QString")) { 0109 return false; 0110 } 0111 0112 return (!lt || Utils::isAscii(lt)) && clazy::contains(methods, clazy::name(method)); 0113 } 0114 0115 // Returns the first occurrence of a QLatin1String(char*) CTOR call 0116 Latin1Expr QStringAllocations::qlatin1CtorExpr(Stmt *stm, ConditionalOperator *&ternary) 0117 { 0118 if (!stm) { 0119 return {}; 0120 } 0121 0122 auto *constructExpr = dyn_cast<CXXConstructExpr>(stm); 0123 if (constructExpr) { 0124 CXXConstructorDecl *ctor = constructExpr->getConstructor(); 0125 const int numArgs = ctor->getNumParams(); 0126 if (clazy::isOfClass(ctor, "QLatin1String")) { 0127 if (Utils::containsStringLiteral(constructExpr, /*allowEmpty=*/false, 2)) { 0128 return {constructExpr, /*enableFixits=*/numArgs == 1}; 0129 } 0130 0131 if (Utils::userDefinedLiteral(constructExpr, "QLatin1String", lo())) { 0132 return {constructExpr, /*enableFixits=*/false}; 0133 } 0134 } 0135 } 0136 0137 // C++17 elides the QLatin1String constructor 0138 if (Utils::userDefinedLiteral(stm, "QLatin1String", lo())) { 0139 return {constructExpr, /*enableFixits=*/false}; 0140 } 0141 0142 if (!ternary) { 0143 ternary = dyn_cast<ConditionalOperator>(stm); 0144 } 0145 0146 for (auto *child : stm->children()) { 0147 auto expr = qlatin1CtorExpr(child, ternary); 0148 if (expr.isValid()) { 0149 return expr; 0150 } 0151 } 0152 0153 return {}; 0154 } 0155 0156 // Returns true if there's a literal in the hierarchy, but aborts if it's parented on CallExpr 0157 // so, returns true for: QLatin1String("foo") but false for QLatin1String(indirection("foo")); 0158 // 0159 static bool containsStringLiteralNoCallExpr(Stmt *stmt) 0160 { 0161 if (!stmt) { 0162 return false; 0163 } 0164 0165 auto *sl = dyn_cast<StringLiteral>(stmt); 0166 if (sl) { 0167 return true; 0168 } 0169 0170 for (auto *child : stmt->children()) { 0171 if (!child) { 0172 continue; 0173 } 0174 auto *callExpr = dyn_cast<CallExpr>(child); 0175 if (!callExpr && containsStringLiteralNoCallExpr(child)) { 0176 return true; 0177 } 0178 } 0179 0180 return false; 0181 } 0182 0183 // For QString::fromLatin1("foo") returns "foo" 0184 static StringLiteral *stringLiteralForCall(Stmt *call) 0185 { 0186 if (!call) { 0187 return nullptr; 0188 } 0189 0190 std::vector<StringLiteral *> literals; 0191 clazy::getChilds(call, literals, 3); 0192 return literals.empty() ? nullptr : literals[0]; 0193 } 0194 0195 void QStringAllocations::VisitCtor(Stmt *stm) 0196 { 0197 auto *ctorExpr = dyn_cast<CXXConstructExpr>(stm); 0198 if (!ctorExpr) { 0199 return; 0200 } 0201 0202 if (!Utils::containsStringLiteral(ctorExpr, /**allowEmpty=*/true)) { 0203 return; 0204 } 0205 0206 // With llvm 10, for some reason, the child CXXConstructExpr of QStringList foo = {"foo}; aren't visited :(. 0207 // Do it manually. 0208 if (clazy::isOfClass(ctorExpr->getConstructor(), "QStringList") 0209 || ctorExpr->getConstructor()->getQualifiedNameAsString() == "QList<QString>::QList") { // In Qt6, QStringList is an alias 0210 auto *p = clazy::getFirstChildOfType2<CXXConstructExpr>(ctorExpr); 0211 while (p) { 0212 if (clazy::isOfClass(p, "QString")) { 0213 VisitCtor(p); 0214 } 0215 p = clazy::getFirstChildOfType2<CXXConstructExpr>(p); 0216 } 0217 } else { 0218 VisitCtor(ctorExpr); 0219 } 0220 } 0221 0222 void QStringAllocations::VisitCtor(CXXConstructExpr *ctorExpr) 0223 { 0224 CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor(); 0225 if (!clazy::isOfClass(ctorDecl, "QString")) { 0226 return; 0227 } 0228 0229 if (Utils::insideCTORCall(m_context->parentMap, ctorExpr, {"QRegExp", "QIcon"})) { 0230 // https://blogs.kde.org/2015/11/05/qregexp-qstringliteral-crash-exit 0231 return; 0232 } 0233 0234 if (!isOptionSet("no-msvc-compat")) { 0235 auto *initializerList = clazy::getFirstParentOfType<InitListExpr>(m_context->parentMap, ctorExpr); 0236 if (initializerList != nullptr) { 0237 return; // Nothing to do here, MSVC doesn't like it 0238 } 0239 0240 StringLiteral *lt = stringLiteralForCall(ctorExpr); 0241 if (lt && lt->getNumConcatenated() > 1) { 0242 return; // Nothing to do here, MSVC doesn't like it 0243 } 0244 } 0245 0246 bool isQLatin1String = false; 0247 std::string paramType; 0248 if (hasCharPtrArgument(ctorDecl, 1)) { 0249 paramType = "const char*"; 0250 } else if (ctorDecl->param_size() == 1 0251 && (clazy::hasArgumentOfType(ctorDecl, "QLatin1String", lo()) || clazy::hasArgumentOfType(ctorDecl, "QLatin1StringView", lo()))) { 0252 paramType = "QLatin1String"; 0253 isQLatin1String = true; 0254 } else { 0255 return; 0256 } 0257 0258 std::string msg = std::string("QString(") + paramType + std::string(") being called"); 0259 0260 if (isQLatin1String) { 0261 ConditionalOperator *ternary = nullptr; 0262 Latin1Expr qlatin1expr = qlatin1CtorExpr(ctorExpr, ternary); 0263 if (!qlatin1expr.isValid()) { 0264 return; 0265 } 0266 0267 auto *qlatin1Ctor = qlatin1expr.qlatin1ctorexpr; 0268 0269 if (qlatin1Ctor->getBeginLoc().isMacroID()) { 0270 auto macroName = Lexer::getImmediateMacroName(qlatin1Ctor->getBeginLoc(), sm(), lo()); 0271 if (macroName == "Q_GLOBAL_STATIC_WITH_ARGS") { // bug #391807 0272 return; 0273 } 0274 } 0275 0276 std::vector<FixItHint> fixits; 0277 if (qlatin1expr.enableFixit) { 0278 if (!qlatin1Ctor->getBeginLoc().isMacroID()) { 0279 if (!ternary) { 0280 fixits = fixItReplaceWordWithWord(qlatin1Ctor, "QStringLiteral", "QLatin1String"); 0281 bool shouldRemoveQString = qlatin1Ctor->getBeginLoc().getRawEncoding() != ctorExpr->getBeginLoc().getRawEncoding() 0282 && dyn_cast_or_null<CXXBindTemporaryExpr>(clazy::parent(m_context->parentMap, ctorExpr)); 0283 if (shouldRemoveQString) { 0284 // This is the case of QString(QLatin1String("foo")), which we just fixed to be QString(QStringLiteral("foo")), so now remove QString 0285 auto removalFixits = clazy::fixItRemoveToken(&m_astContext, ctorExpr, true); 0286 if (removalFixits.empty()) { 0287 queueManualFixitWarning(ctorExpr->getBeginLoc(), "Internal error: invalid start or end location"); 0288 } else { 0289 clazy::append(removalFixits, fixits); 0290 } 0291 } 0292 } else { 0293 fixits = fixItReplaceWordWithWordInTernary(ternary); 0294 } 0295 } else { 0296 queueManualFixitWarning(qlatin1Ctor->getBeginLoc(), "Can't use QStringLiteral in macro"); 0297 } 0298 } 0299 0300 maybeEmitWarning(ctorExpr->getBeginLoc(), msg, fixits); 0301 } else { 0302 std::vector<FixItHint> fixits; 0303 if (clazy::hasChildren(ctorExpr)) { 0304 auto *pointerDecay = dyn_cast<ImplicitCastExpr>(*(ctorExpr->child_begin())); 0305 if (clazy::hasChildren(pointerDecay)) { 0306 auto *lt = dyn_cast<StringLiteral>(*pointerDecay->child_begin()); 0307 if (lt) { 0308 Stmt *grandParent = clazy::parent(m_context->parentMap, lt, 2); 0309 Stmt *grandGrandParent = clazy::parent(m_context->parentMap, lt, 3); 0310 Stmt *grandGrandGrandParent = clazy::parent(m_context->parentMap, lt, 4); 0311 if (grandParent == ctorExpr && grandGrandParent && isa<CXXBindTemporaryExpr>(grandGrandParent) && grandGrandGrandParent 0312 && isa<CXXFunctionalCastExpr>(grandGrandGrandParent)) { 0313 // This is the case of QString("foo"), replace QString 0314 0315 const bool literalIsEmpty = lt->getLength() == 0; 0316 if (literalIsEmpty && clazy::getFirstParentOfType<MemberExpr>(m_context->parentMap, ctorExpr) == nullptr) { 0317 fixits = fixItReplaceWordWithWord(ctorExpr, "QLatin1String", "QString"); 0318 } else if (!ctorExpr->getBeginLoc().isMacroID()) { 0319 fixits = fixItReplaceWordWithWord(ctorExpr, "QStringLiteral", "QString"); 0320 } else { 0321 queueManualFixitWarning(ctorExpr->getBeginLoc(), "Can't use QStringLiteral in macro."); 0322 } 0323 } else { 0324 auto *parentMemberCallExpr = 0325 clazy::getFirstParentOfType<CXXMemberCallExpr>(m_context->parentMap, 0326 lt, 0327 /*maxDepth=*/6); // 6 seems like a nice max from the ASTs I've seen 0328 0329 std::string replacement = "QStringLiteral"; 0330 if (parentMemberCallExpr) { 0331 FunctionDecl *fDecl = parentMemberCallExpr->getDirectCallee(); 0332 if (fDecl) { 0333 auto *method = dyn_cast<CXXMethodDecl>(fDecl); 0334 if (method && betterTakeQLatin1String(method, lt)) { 0335 replacement = "QLatin1String"; 0336 } 0337 } 0338 } 0339 0340 fixits = fixItRawLiteral(lt, replacement, nullptr); 0341 } 0342 } 0343 } 0344 } 0345 0346 maybeEmitWarning(ctorExpr->getBeginLoc(), msg, fixits); 0347 } 0348 } 0349 0350 std::vector<FixItHint> QStringAllocations::fixItReplaceWordWithWord(clang::Stmt *begin, const std::string &replacement, const std::string &replacee) 0351 { 0352 StringLiteral *lt = stringLiteralForCall(begin); 0353 if (replacee == "QLatin1String") { 0354 if (lt && !Utils::isAscii(lt)) { 0355 maybeEmitWarning(lt->getBeginLoc(), "Don't use QLatin1String with non-latin1 literals"); 0356 return {}; 0357 } 0358 } 0359 0360 if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) { 0361 return {}; 0362 } 0363 0364 std::vector<FixItHint> fixits; 0365 FixItHint fixit = clazy::fixItReplaceWordWithWord(&m_astContext, begin, replacement, replacee); 0366 if (fixit.isNull()) { 0367 queueManualFixitWarning(begin->getBeginLoc(), ""); 0368 } else { 0369 fixits.push_back(fixit); 0370 } 0371 0372 return fixits; 0373 } 0374 0375 std::vector<FixItHint> QStringAllocations::fixItReplaceWordWithWordInTernary(clang::ConditionalOperator *ternary) 0376 { 0377 std::vector<CXXConstructExpr *> constructExprs; 0378 0379 auto addConstructExpr = [&constructExprs](Expr *expr) { 0380 if (auto *functionalCast = dyn_cast<CXXFunctionalCastExpr>(expr)) { 0381 expr = functionalCast->getSubExpr(); 0382 } 0383 0384 if (auto *constructExpr = dyn_cast<CXXConstructExpr>(expr)) { 0385 constructExprs.push_back(constructExpr); 0386 } 0387 }; 0388 0389 addConstructExpr(ternary->getTrueExpr()); 0390 addConstructExpr(ternary->getFalseExpr()); 0391 0392 if (constructExprs.size() != 2) { 0393 llvm::errs() << "Weird ternary operator with " << constructExprs.size() << " constructExprs at " << ternary->getBeginLoc().printToString(sm()) << "\n"; 0394 ternary->dump(); 0395 assert(false); 0396 return {}; 0397 } 0398 0399 std::vector<FixItHint> fixits; 0400 fixits.reserve(2); 0401 for (CXXConstructExpr *constructExpr : constructExprs) { 0402 SourceLocation rangeStart = constructExpr->getBeginLoc(); 0403 SourceLocation rangeEnd = Lexer::getLocForEndOfToken(rangeStart, -1, sm(), lo()); 0404 fixits.push_back(FixItHint::CreateReplacement(SourceRange(rangeStart, rangeEnd), "QStringLiteral")); 0405 } 0406 0407 return fixits; 0408 } 0409 0410 // true for: QString::fromLatin1().arg() 0411 // false for: QString::fromLatin1("") 0412 // true for: QString s = QString::fromLatin1("foo") 0413 // false for: s += QString::fromLatin1("foo"), etc. 0414 static bool isQStringLiteralCandidate(Stmt *s, ParentMap *map, const LangOptions &lo, const SourceManager &sm, int currentCall = 0) 0415 { 0416 if (!s) { 0417 return false; 0418 } 0419 0420 auto *memberExpr = dyn_cast<MemberExpr>(s); 0421 if (memberExpr) { 0422 return true; 0423 } 0424 0425 auto *constructExpr = dyn_cast<CXXConstructExpr>(s); 0426 if (clazy::isOfClass(constructExpr, "QString")) { 0427 return true; 0428 } 0429 0430 if (Utils::isAssignOperator(dyn_cast<CXXOperatorCallExpr>(s), "QString", "QLatin1String", lo)) { 0431 return true; 0432 } 0433 0434 if (Utils::isAssignOperator(dyn_cast<CXXOperatorCallExpr>(s), "QString", "QString", lo)) { 0435 return true; 0436 } 0437 0438 auto *callExpr = dyn_cast<CallExpr>(s); 0439 StringLiteral *literal = stringLiteralForCall(callExpr); 0440 auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(s); 0441 if (operatorCall && clazy::returnTypeName(operatorCall, lo) != "QTestData") { 0442 // QTest::newRow will static_assert when using QLatin1String 0443 // Q_STATIC_ASSERT_X(QMetaTypeId2<T>::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object 0444 // system"); 0445 0446 std::string className = clazy::classNameFor(operatorCall); 0447 if (className == "QString") { 0448 return false; 0449 } 0450 if (className.empty() && clazy::hasArgumentOfType(operatorCall->getDirectCallee(), "QString", lo)) { 0451 return false; 0452 } 0453 } 0454 0455 // C++17 elides the QString constructor call in QString s = QString::fromLatin1("foo"); 0456 if (currentCall > 0) { 0457 auto exprWithCleanups = dyn_cast<ExprWithCleanups>(s); 0458 if (exprWithCleanups) { 0459 if (auto bindTemp = dyn_cast<CXXBindTemporaryExpr>(exprWithCleanups->getSubExpr())) { 0460 if (dyn_cast<CallExpr>(bindTemp->getSubExpr())) 0461 return true; 0462 } 0463 } 0464 } 0465 0466 if (currentCall > 0 && callExpr) { 0467 auto *fDecl = callExpr->getDirectCallee(); 0468 return !(fDecl && betterTakeQLatin1String(dyn_cast<CXXMethodDecl>(fDecl), literal)); 0469 } 0470 0471 if (currentCall == 0 || dyn_cast<ImplicitCastExpr>(s) || dyn_cast<CXXBindTemporaryExpr>(s) 0472 || dyn_cast<MaterializeTemporaryExpr>(s)) { // recurse over this cruft 0473 return isQStringLiteralCandidate(clazy::parent(map, s), map, lo, sm, currentCall + 1); 0474 } 0475 0476 return false; 0477 } 0478 0479 std::vector<FixItHint> QStringAllocations::fixItReplaceFromLatin1OrFromUtf8(CallExpr *callExpr, FromFunction fromFunction) 0480 { 0481 std::vector<FixItHint> fixits; 0482 0483 std::string replacement = isQStringLiteralCandidate(callExpr, m_context->parentMap, lo(), sm()) ? "QStringLiteral" : "QLatin1String"; 0484 if (replacement == "QStringLiteral" && callExpr->getBeginLoc().isMacroID()) { 0485 queueManualFixitWarning(callExpr->getBeginLoc(), "Can't use QStringLiteral in macro!"); 0486 return {}; 0487 } 0488 0489 StringLiteral *literal = stringLiteralForCall(callExpr); 0490 if (literal) { 0491 if (Utils::literalContainsEscapedBytes(literal, sm(), lo())) { 0492 return {}; 0493 } 0494 if (!Utils::isAscii(literal)) { 0495 // QString::fromLatin1() to QLatin1String() is fine 0496 // QString::fromUtf8() to QStringLiteral() is fine 0497 // all other combinations are not 0498 if (replacement == "QStringLiteral" && fromFunction == FromLatin1) { 0499 return {}; 0500 } 0501 if (replacement == "QLatin1String" && fromFunction == FromUtf8) { 0502 replacement = "QStringLiteral"; 0503 } 0504 } 0505 0506 auto classNameLoc = Lexer::getLocForEndOfToken(callExpr->getBeginLoc(), 0, sm(), lo()); 0507 auto scopeOperatorLoc = Lexer::getLocForEndOfToken(classNameLoc, 0, sm(), lo()); 0508 auto methodNameLoc = Lexer::getLocForEndOfToken(scopeOperatorLoc, -1, sm(), lo()); 0509 SourceRange range(callExpr->getBeginLoc(), methodNameLoc); 0510 fixits.push_back(FixItHint::CreateReplacement(range, replacement)); 0511 } else { 0512 queueManualFixitWarning(callExpr->getBeginLoc(), "Internal error: literal is null"); 0513 } 0514 0515 return fixits; 0516 } 0517 0518 namespace 0519 { 0520 // Start at <loc> and go left as long as there's whitespace, stopping at <start> in the worst case 0521 SourceLocation eatLeadingWhitespace(SourceLocation start, SourceLocation loc, const SourceManager &sm, const LangOptions &lo) 0522 { 0523 const SourceRange range(start, loc); 0524 const CharSourceRange cr = Lexer::getAsCharRange(range, sm, lo); 0525 const StringRef str = Lexer::getSourceText(cr, sm, lo); 0526 const int initialPos = sm.getDecomposedLoc(loc).second - sm.getDecomposedLoc(start).second; 0527 int i = initialPos; 0528 while (--i >= 0) { 0529 if (!isHorizontalWhitespace(str[i])) 0530 return loc.getLocWithOffset(i - initialPos + 1); 0531 } 0532 return loc; 0533 } 0534 } 0535 0536 std::vector<FixItHint> QStringAllocations::fixItRawLiteral(StringLiteral *lt, const std::string &replacement, CXXOperatorCallExpr *operatorCall) 0537 { 0538 std::vector<FixItHint> fixits; 0539 0540 SourceRange range = clazy::rangeForLiteral(&m_astContext, lt); 0541 if (range.isInvalid()) { 0542 if (lt) { 0543 queueManualFixitWarning(lt->getBeginLoc(), "Internal error: Can't calculate source location"); 0544 } 0545 return {}; 0546 } 0547 0548 SourceLocation start = lt->getBeginLoc(); 0549 if (start.isMacroID()) { 0550 queueManualFixitWarning(start, "Can't use QStringLiteral in macro"); 0551 } else { 0552 if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) { 0553 return {}; 0554 } 0555 0556 // Turn str == "" into str.isEmpty() 0557 if (operatorCall && operatorCall->getOperator() == OO_EqualEqual && lt->getLength() == 0) { 0558 // For some reason this returns the same as getStartLoc 0559 // SourceLocation start = operatorCall->getArg(0)->getEndLoc(); 0560 // So instead, we have to start from the "==" sign and eat whitespace to the left 0561 SourceLocation start = eatLeadingWhitespace(operatorCall->getBeginLoc(), operatorCall->getExprLoc(), sm(), lo()); 0562 fixits.push_back(clazy::createReplacement({start, range.getEnd()}, ".isEmpty()")); 0563 return fixits; 0564 } 0565 0566 // Turn str != "" into !str.isEmpty() 0567 if (operatorCall && operatorCall->getOperator() == OO_ExclaimEqual && lt->getLength() == 0) { 0568 // For some reason this returns the same as getStartLoc 0569 // SourceLocation start = operatorCall->getArg(0)->getEndLoc(); 0570 // So instead, we have to start from the "==" sign and eat whitespace to the left 0571 SourceLocation start = eatLeadingWhitespace(operatorCall->getBeginLoc(), operatorCall->getExprLoc(), sm(), lo()); 0572 fixits.push_back(clazy::createReplacement({start, range.getEnd()}, ".isEmpty()")); 0573 fixits.push_back(clazy::createInsertion(operatorCall->getBeginLoc(), "!")); 0574 return fixits; 0575 } 0576 0577 std::string revisedReplacement = lt->getLength() == 0 ? "QLatin1String" : replacement; // QLatin1String("") is better than QStringLiteral("") 0578 if (revisedReplacement == "QStringLiteral" && lt->getBeginLoc().isMacroID()) { 0579 queueManualFixitWarning(lt->getBeginLoc(), "Can't use QStringLiteral in macro..."); 0580 return {}; 0581 } 0582 0583 clazy::insertParentMethodCall(revisedReplacement, range, /**by-ref*/ fixits); 0584 } 0585 0586 return fixits; 0587 } 0588 0589 void QStringAllocations::VisitOperatorCall(Stmt *stm) 0590 { 0591 auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(stm); 0592 if (!operatorCall) { 0593 return; 0594 } 0595 0596 if (clazy::returnTypeName(operatorCall, lo()) == "QTestData") { 0597 // QTest::newRow will static_assert when using QLatin1String 0598 // Q_STATIC_ASSERT_X(QMetaTypeId2<T>::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object 0599 // system"); 0600 return; 0601 } 0602 0603 std::vector<StringLiteral *> stringLiterals; 0604 clazy::getChilds<StringLiteral>(operatorCall, stringLiterals); 0605 0606 // We're only after string literals, str.contains(some_method_returning_const_char_is_fine()) 0607 if (stringLiterals.empty()) { 0608 return; 0609 } 0610 0611 FunctionDecl *funcDecl = operatorCall->getDirectCallee(); 0612 if (!funcDecl) { 0613 return; 0614 } 0615 0616 auto *methodDecl = dyn_cast<CXXMethodDecl>(funcDecl); 0617 if (!clazy::isOfClass(methodDecl, "QString")) { 0618 return; 0619 } 0620 0621 if (!hasCharPtrArgument(methodDecl)) { 0622 return; 0623 } 0624 0625 std::vector<FixItHint> fixits; 0626 0627 std::vector<StringLiteral *> literals; 0628 clazy::getChilds<StringLiteral>(stm, literals, 2); 0629 0630 if (!isOptionSet("no-msvc-compat") && !literals.empty()) { 0631 llvm::errs() << "literal non empty\n"; 0632 if (literals[0]->getNumConcatenated() > 1) { 0633 return; // Nothing to do here, MSVC doesn't like it 0634 } 0635 } 0636 0637 if (literals.empty()) { 0638 queueManualFixitWarning(stm->getBeginLoc(), "Couldn't find literal"); 0639 } else { 0640 const std::string replacement = Utils::isAscii(literals[0]) ? "QLatin1String" : "QStringLiteral"; 0641 fixits = fixItRawLiteral(literals[0], replacement, operatorCall); 0642 } 0643 0644 std::string msg("QString(const char*) being called"); 0645 maybeEmitWarning(stm->getBeginLoc(), msg, fixits); 0646 } 0647 0648 void QStringAllocations::VisitFromLatin1OrUtf8(Stmt *stmt) 0649 { 0650 auto *callExpr = dyn_cast<CallExpr>(stmt); 0651 if (!callExpr) { 0652 return; 0653 } 0654 0655 FunctionDecl *functionDecl = callExpr->getDirectCallee(); 0656 if (!clazy::functionIsOneOf(functionDecl, {"fromLatin1", "fromUtf8"})) { 0657 return; 0658 } 0659 0660 auto *methodDecl = dyn_cast<CXXMethodDecl>(functionDecl); 0661 if (!clazy::isOfClass(methodDecl, "QString")) { 0662 return; 0663 } 0664 0665 bool isKnownLiteralOverload = false; 0666 for (auto e : Utils::functionParameters(functionDecl)) { 0667 if (e->getType().getAsString(lo()) == "QByteArrayView") { 0668 isKnownLiteralOverload = true; 0669 } 0670 } 0671 if (!isKnownLiteralOverload && (!Utils::callHasDefaultArguments(callExpr) || !hasCharPtrArgument(functionDecl, 2))) { // QString::fromLatin1("foo", 1) is ok 0672 return; 0673 } 0674 if (!containsStringLiteralNoCallExpr(callExpr)) { 0675 return; 0676 } 0677 0678 if (!isOptionSet("no-msvc-compat")) { 0679 StringLiteral *lt = stringLiteralForCall(callExpr); 0680 if (lt && lt->getNumConcatenated() > 1) { 0681 return; // Nothing to do here, MSVC doesn't like it 0682 } 0683 } 0684 0685 std::vector<ConditionalOperator *> ternaries; 0686 clazy::getChilds(callExpr, ternaries); // In Qt5 it is always 2 levels down, but in Qt6 more 0687 if (!ternaries.empty()) { 0688 auto *ternary = ternaries[0]; 0689 if (Utils::ternaryOperatorIsOfStringLiteral(ternary)) { 0690 maybeEmitWarning(stmt->getBeginLoc(), std::string("QString::fromLatin1() being passed a literal")); 0691 } 0692 0693 return; 0694 } 0695 0696 const FromFunction fromFunction = clazy::name(functionDecl) == "fromLatin1" ? FromLatin1 : FromUtf8; 0697 const std::vector<FixItHint> fixits = fixItReplaceFromLatin1OrFromUtf8(callExpr, fromFunction); 0698 0699 if (clazy::name(functionDecl) == "fromLatin1") { 0700 maybeEmitWarning(stmt->getBeginLoc(), std::string("QString::fromLatin1() being passed a literal"), fixits); 0701 } else { 0702 maybeEmitWarning(stmt->getBeginLoc(), std::string("QString::fromUtf8() being passed a literal"), fixits); 0703 } 0704 } 0705 0706 void QStringAllocations::VisitAssignOperatorQLatin1String(Stmt *stmt) 0707 { 0708 auto *callExpr = dyn_cast<CXXOperatorCallExpr>(stmt); 0709 if (!callExpr) { 0710 return; 0711 } 0712 if (!Utils::isAssignOperator(callExpr, "QString", "QLatin1String", lo()) && !Utils::isAssignOperator(callExpr, "QString", "QLatin1StringView", lo())) { 0713 return; 0714 } 0715 0716 if (!containsStringLiteralNoCallExpr(stmt)) { 0717 return; 0718 } 0719 0720 ConditionalOperator *ternary = nullptr; 0721 Stmt *begin = qlatin1CtorExpr(stmt, ternary).qlatin1ctorexpr; 0722 0723 if (!begin) { 0724 return; 0725 } 0726 0727 const std::vector<FixItHint> fixits = 0728 ternary == nullptr ? fixItReplaceWordWithWord(begin, "QStringLiteral", "QLatin1String") : fixItReplaceWordWithWordInTernary(ternary); 0729 0730 maybeEmitWarning(stmt->getBeginLoc(), std::string("QString::operator=(QLatin1String(\"literal\")"), fixits); 0731 } 0732 0733 void QStringAllocations::maybeEmitWarning(SourceLocation loc, std::string error, std::vector<FixItHint> fixits) 0734 { 0735 if (clazy::isUIFile(loc, sm())) { 0736 // Don't bother warning for generated UI files. 0737 // We do the check here instead of at the beginning so users that don't use UI files don't have to pay the performance price. 0738 return; 0739 } 0740 0741 if (m_context->isQtDeveloper() && Utils::filenameForLoc(loc, sm()) == "qstring.cpp") { 0742 // There's an error replacing an internal fromLatin1() because the replacement code doesn't expect to be working on QString itself 0743 // not worth to fix, it's only 1 case in qstring.cpp, and related to Qt 1.x compat 0744 fixits = {}; 0745 } 0746 0747 emitWarning(loc, std::move(error), fixits); 0748 }