File indexing completed on 2024-05-12 05:41:03

0001 /*
0002     SPDX-FileCopyrightText: 2020 The Qt Company Ltd.
0003     SPDX-FileCopyrightText: 2020 Lucie Gerard <lucie.gerard@qt.io>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "qt6-qlatin1stringchar-to-u.h"
0009 #include "ClazyContext.h"
0010 #include "FixItUtils.h"
0011 #include "HierarchyUtils.h"
0012 #include "StringUtils.h"
0013 #include "Utils.h"
0014 #include "clazy_stl.h"
0015 
0016 #include <clang/AST/Decl.h>
0017 #include <clang/AST/DeclCXX.h>
0018 #include <clang/AST/Expr.h>
0019 #include <clang/AST/ExprCXX.h>
0020 #include <clang/AST/Stmt.h>
0021 #include <clang/AST/Type.h>
0022 #include <clang/Basic/Diagnostic.h>
0023 #include <clang/Basic/LLVM.h>
0024 #include <clang/Basic/SourceLocation.h>
0025 #include <clang/Lex/Lexer.h>
0026 #include <llvm/ADT/ArrayRef.h>
0027 #include <llvm/ADT/StringRef.h>
0028 #include <llvm/Support/Casting.h>
0029 
0030 using namespace clang;
0031 
0032 Qt6QLatin1StringCharToU::Qt6QLatin1StringCharToU(const std::string &name, ClazyContext *context)
0033     : CheckBase(name, context, Option_CanIgnoreIncludes)
0034 {
0035     enablePreProcessorCallbacks();
0036 }
0037 
0038 static bool isQLatin1CharDecl(CXXConstructorDecl *decl)
0039 {
0040     return decl && clazy::isOfClass(decl, "QLatin1Char");
0041 }
0042 
0043 static bool isQLatin1StringDecl(CXXConstructorDecl *decl)
0044 {
0045     return decl && clazy::isOfClass(decl, "QLatin1String");
0046 }
0047 
0048 bool Qt6QLatin1StringCharToU::foundQCharOrQString(Stmt *stmt)
0049 {
0050     QualType type;
0051     if (auto *init = dyn_cast<InitListExpr>(stmt)) {
0052         type = init->getType();
0053     } else if (auto *opp = dyn_cast<CXXOperatorCallExpr>(stmt)) {
0054         type = opp->getType();
0055     } else if (auto *constr = dyn_cast<CXXConstructExpr>(stmt)) {
0056         type = constr->getType();
0057     } else if (auto *decl = dyn_cast<DeclRefExpr>(stmt)) {
0058         type = decl->getType();
0059     } else if (auto *func = dyn_cast<CXXFunctionalCastExpr>(stmt)) {
0060         type = func->getType();
0061     } else if (dyn_cast<CXXMemberCallExpr>(stmt)) {
0062         Stmt *child = clazy::childAt(stmt, 0);
0063         while (child) {
0064             if (foundQCharOrQString(child)) {
0065                 return true;
0066             }
0067             child = clazy::childAt(child, 0);
0068         }
0069     }
0070 
0071     if (auto *ptr = type.getTypePtrOrNull(); !ptr || (!ptr->isRecordType() && !ptr->isConstantArrayType())) {
0072         return false;
0073     }
0074     std::string typeStr = type.getAsString(lo());
0075     return typeStr.find("QString") != std::string::npos || typeStr.find("QChar") != std::string::npos;
0076 }
0077 
0078 bool Qt6QLatin1StringCharToU::relatedToQStringOrQChar(Stmt *stmt, const ClazyContext *const context)
0079 {
0080     if (!stmt) {
0081         return false;
0082     }
0083 
0084     while (stmt) {
0085         if (foundQCharOrQString(stmt)) {
0086             return true;
0087         }
0088 
0089         stmt = clazy::parent(context->parentMap, stmt);
0090     }
0091 
0092     return false;
0093 }
0094 
0095 /*
0096  * To be interesting, the CXXContructExpr:
0097  * 1/ must be of class QLatin1String
0098  * 2/ must have a CXXFunctionalCastExpr with name QLatin1String
0099  *    (to pick only one of two the CXXContructExpr of class QLatin1String)
0100  * 3/ must not be nested within an other QLatin1String call (unless looking for left over)
0101  *    This is done by looking for CXXFunctionalCastExpr with name QLatin1String among parents
0102  *    QLatin1String call nesting in other QLatin1String call are treated while visiting the outer call.
0103  */
0104 bool Qt6QLatin1StringCharToU::isInterestingCtorCall(CXXConstructExpr *ctorExpr, const ClazyContext *const context, bool check_parent)
0105 {
0106     CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor();
0107     if (!isQLatin1CharDecl(ctorDecl) && !isQLatin1StringDecl(ctorDecl)) {
0108         return false;
0109     }
0110 
0111     Stmt *parent_stmt = clazy::parent(context->parentMap, ctorExpr);
0112     if (!parent_stmt) {
0113         return false;
0114     }
0115     bool oneFunctionalCast = false;
0116     // A given QLatin1Char/String call will have two ctorExpr passing the isQLatin1CharDecl/StringDecl
0117     // To avoid creating multiple fixit in case of nested QLatin1Char/String calls
0118     // it is important to only test the one right after a CXXFunctionalCastExpr with QLatin1Char/String name
0119     if (isa<CXXFunctionalCastExpr>(parent_stmt)) {
0120         auto *parent = dyn_cast<CXXFunctionalCastExpr>(parent_stmt);
0121         if (parent->getConversionFunction()->getNameAsString() != "QLatin1Char" && parent->getConversionFunction()->getNameAsString() != "QLatin1String") {
0122             return false;
0123         } // need to check that this call is related to a QString or a QChar
0124         if (check_parent) {
0125             m_QStringOrQChar_fix = relatedToQStringOrQChar(parent_stmt, context);
0126         }
0127         // in case of looking for left over, we don't do it here, because might go past the QLatin1Char/String we are nested in
0128         // and replace the one within
0129         // QString toto = QLatin1String ( something_not_supported ? QLatin1String("should not be corrected") : "toto" )
0130         // the inside one should not be corrected because the outside QLatin1String is staying.
0131         m_QChar = parent->getConversionFunction()->getNameAsString() == "QLatin1Char";
0132 
0133         oneFunctionalCast = true;
0134     }
0135 
0136     // Not checking the parent when looking for left over QLatin1String call nested in a QLatin1String whose fix is not supported
0137     if (!check_parent) {
0138         return oneFunctionalCast;
0139     }
0140 
0141     parent_stmt = context->parentMap->getParent(parent_stmt);
0142     // If an other CXXFunctionalCastExpr QLatin1String is found among the parents
0143     // the present QLatin1String call is nested in an other QLatin1String call and should be ignored.
0144     // The outer call will take care of it.
0145     // Unless the outer call is from a Macro, in which case the current call should not be ignored
0146     while (parent_stmt) {
0147         if (isa<CXXFunctionalCastExpr>(parent_stmt)) {
0148             auto *parent = dyn_cast<CXXFunctionalCastExpr>(parent_stmt);
0149             NamedDecl *ndecl = parent->getConversionFunction();
0150             if (ndecl) {
0151                 if (ndecl->getNameAsString() == "QLatin1Char" || ndecl->getNameAsString() == "QLatin1String") {
0152                     if (parent_stmt->getBeginLoc().isMacroID()) {
0153                         auto parent_stmt_begin = parent_stmt->getBeginLoc();
0154                         auto parent_stmt_end = parent_stmt->getEndLoc();
0155                         auto parent_spl_begin = sm().getSpellingLoc(parent_stmt_begin);
0156                         auto parent_spl_end = sm().getSpellingLoc(parent_stmt_end);
0157                         auto ctorSpelling_loc = sm().getSpellingLoc(ctorExpr->getBeginLoc());
0158                         if (m_sm.isPointWithin(ctorSpelling_loc, parent_spl_begin, parent_spl_end)) {
0159                             return false;
0160                         }
0161                         return oneFunctionalCast;
0162                     }
0163 
0164                     return false;
0165                 }
0166             }
0167         }
0168         parent_stmt = context->parentMap->getParent(parent_stmt);
0169     }
0170 
0171     return oneFunctionalCast;
0172 }
0173 
0174 bool Qt6QLatin1StringCharToU::warningAlreadyEmitted(SourceLocation sploc)
0175 {
0176     return std::find(m_emittedWarningsInMacro.begin(), m_emittedWarningsInMacro.end(), sploc) != m_emittedWarningsInMacro.end();
0177 }
0178 
0179 void Qt6QLatin1StringCharToU::VisitStmt(clang::Stmt *stmt)
0180 {
0181     auto *ctorExpr = dyn_cast<CXXConstructExpr>(stmt);
0182     if (!ctorExpr) {
0183         return;
0184     }
0185     m_QStringOrQChar_fix = false;
0186     if (!isInterestingCtorCall(ctorExpr, m_context, true)) {
0187         return;
0188     }
0189 
0190     std::vector<FixItHint> fixits;
0191     std::string message;
0192 
0193     for (auto macro_pos : m_listingMacroExpand) {
0194         if (m_sm.isPointWithin(macro_pos, stmt->getBeginLoc(), stmt->getEndLoc())) {
0195             message = "QLatin1Char or QLatin1String is being called (fix it not supported because of macro)";
0196             emitWarning(stmt->getBeginLoc(), message, fixits);
0197             return;
0198         }
0199     }
0200     if (!m_QStringOrQChar_fix) {
0201         message = "QLatin1Char or QLatin1String is being called (fix it not supported)";
0202         emitWarning(stmt->getBeginLoc(), message, fixits);
0203         return;
0204     }
0205 
0206     checkCTorExpr(stmt, true);
0207 }
0208 
0209 bool Qt6QLatin1StringCharToU::checkCTorExpr(clang::Stmt *stmt, bool check_parents)
0210 {
0211     auto *ctorExpr = dyn_cast<CXXConstructExpr>(stmt);
0212     if (!ctorExpr) {
0213         return false;
0214     }
0215 
0216     std::vector<FixItHint> fixits;
0217     std::string message;
0218 
0219     // parents are not checked when looking inside a QLatin1Char/String that does not support fixes
0220     // extra parentheses might be needed for the inner QLatin1Char/String fix
0221     bool extra_parentheses = !check_parents;
0222 
0223     bool noFix = false;
0224 
0225     SourceLocation warningLocation = stmt->getBeginLoc();
0226 
0227     if (!isInterestingCtorCall(ctorExpr, m_context, check_parents)) {
0228         return false;
0229     }
0230     message = "QLatin1Char or QLatin1String is being called";
0231     if (stmt->getBeginLoc().isMacroID()) {
0232         SourceLocation callLoc = stmt->getBeginLoc();
0233         message += " in macro ";
0234         message += Lexer::getImmediateMacroName(callLoc, m_sm, lo());
0235         message += ". Please replace with `u` call manually.";
0236         SourceLocation sploc = sm().getSpellingLoc(callLoc);
0237         warningLocation = sploc;
0238         if (warningAlreadyEmitted(sploc)) {
0239             return false;
0240         }
0241 
0242         m_emittedWarningsInMacro.push_back(sploc);
0243         // We don't support fixit within macro. (because the replacement is wrong within the #define)
0244         emitWarning(sploc, message, fixits);
0245         return true;
0246     }
0247 
0248     std::string replacement = buildReplacement(stmt, noFix, extra_parentheses);
0249     if (!noFix) {
0250         fixits.push_back(FixItHint::CreateReplacement(stmt->getSourceRange(), replacement));
0251     }
0252 
0253     emitWarning(warningLocation, message, fixits);
0254 
0255     if (noFix) {
0256         m_QChar_noFix = m_QChar; // because QLatin1Char with QLatin1Char whose fix is unsupported should be corrected
0257                                  // unlike QLatin1String
0258         lookForLeftOver(stmt, m_QChar);
0259     }
0260 
0261     return true;
0262 }
0263 
0264 void Qt6QLatin1StringCharToU::lookForLeftOver(clang::Stmt *stmt, bool found_QString_QChar)
0265 {
0266     Stmt *current_stmt = stmt;
0267     bool keep_looking = true;
0268     // remebering the QString or QChar trace from the other sibbling in case of CXXMemberCallExpr
0269     // in order to catch QLatin1String("notcaught") in the following exemple
0270     // s1 = QLatin1String(s2df.contains(QLatin1String("notcaught"))? QLatin1String("dontfix1") : QLatin1String("dontfix2"));
0271     bool remember = false;
0272     if (isa<CXXMemberCallExpr>(current_stmt)) {
0273         remember = true;
0274     }
0275     for (auto it = current_stmt->child_begin(); it != current_stmt->child_end(); it++) {
0276         Stmt *child = *it;
0277 
0278         // here need to make sure a QChar or QString type is present between the first current_stmt and the one we are testing
0279         // should not check the parents because we might go past the QLatin1String or QLatin1Char whose fix was not supported
0280         if (!found_QString_QChar) {
0281             found_QString_QChar = foundQCharOrQString(child);
0282         }
0283 
0284         // if no QString or QChar signature as been found, no point to check for QLatin1String or QLatin1Char to correct.
0285         if (found_QString_QChar) {
0286             keep_looking = !checkCTorExpr(child, false); // if QLatin1Char/String is found, stop looking into children of current child
0287         }
0288         // the QLatin1Char/String calls present there, if any, will be caught
0289         if (keep_looking) {
0290             lookForLeftOver(child, found_QString_QChar);
0291         }
0292 
0293         if (!remember) {
0294             found_QString_QChar = m_QChar_noFix;
0295         }
0296     }
0297 }
0298 
0299 std::string Qt6QLatin1StringCharToU::buildReplacement(clang::Stmt *stmt, bool &noFix, bool extra, bool ancestorIsCondition, int ancestorConditionChildNumber)
0300 {
0301     std::string replacement;
0302     Stmt *current_stmt = stmt;
0303 
0304     int i = 0;
0305 
0306     for (auto it = current_stmt->child_begin(); it != current_stmt->child_end(); it++) {
0307         Stmt *child = *it;
0308         auto *parent_condOp = dyn_cast<ConditionalOperator>(current_stmt);
0309         auto *child_condOp = dyn_cast<ConditionalOperator>(child);
0310 
0311         if (parent_condOp) {
0312             ancestorIsCondition = true;
0313             ancestorConditionChildNumber = i;
0314             if (ancestorConditionChildNumber == 2) {
0315                 replacement += " : ";
0316             }
0317         }
0318 
0319         // to handle nested condition
0320         if (child_condOp && ancestorIsCondition) {
0321             replacement += "(";
0322         }
0323 
0324         // to handle catching left over nested QLatin1String call
0325         if (extra && child_condOp && !ancestorIsCondition) {
0326             replacement += "(";
0327         }
0328 
0329         replacement += buildReplacement(child, noFix, extra, ancestorIsCondition, ancestorConditionChildNumber);
0330 
0331         auto *child_declRefExp = dyn_cast<DeclRefExpr>(child);
0332         auto *child_boolLitExp = dyn_cast<CXXBoolLiteralExpr>(child);
0333         auto *child_charliteral = dyn_cast<CharacterLiteral>(child);
0334         auto *child_stringliteral = dyn_cast<StringLiteral>(child);
0335 
0336         if (child_stringliteral) {
0337             replacement += "u\"";
0338             replacement += child_stringliteral->getString();
0339             replacement += "\"";
0340             replacement += "_qs";
0341         } else if (child_charliteral) {
0342             replacement += "u\'";
0343             if (child_charliteral->getValue() == 92 || child_charliteral->getValue() == 39) {
0344                 replacement += "\\";
0345             }
0346             replacement += child_charliteral->getValue();
0347             replacement += "\'";
0348         } else if (child_boolLitExp) {
0349             replacement = child_boolLitExp->getValue() ? "true" : "false";
0350             replacement += " ? ";
0351         } else if (child_declRefExp) {
0352             if (ancestorIsCondition && ancestorConditionChildNumber == 0 && child_declRefExp->getType().getAsString() == "_Bool") {
0353                 replacement += child_declRefExp->getNameInfo().getAsString();
0354                 replacement += " ? ";
0355             } else {
0356                 // not supporting those cases
0357                 noFix = true;
0358                 return {};
0359             }
0360         } else if (child_condOp && ancestorIsCondition) {
0361             replacement += ")";
0362         }
0363 
0364         if (extra && child_condOp && !ancestorIsCondition) {
0365             replacement += ")";
0366         }
0367 
0368         i++;
0369     }
0370     return replacement;
0371 }
0372 
0373 void Qt6QLatin1StringCharToU::VisitMacroExpands(const clang::Token & /*MacroNameTok*/, const clang::SourceRange &range, const MacroInfo * /*info*/)
0374 {
0375     m_listingMacroExpand.push_back(range.getBegin());
0376     return;
0377 }