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 }