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 }