File indexing completed on 2024-05-12 05:40:56
0001 /* 0002 SPDX-FileCopyrightText: 2017 Sergio Martins <smartins@kde.org> 0003 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "qcolor-from-literal.h" 0009 #include "HierarchyUtils.h" 0010 #include "StringUtils.h" 0011 0012 #include <cctype> 0013 #include <clang/AST/Expr.h> 0014 #include <clang/AST/ExprCXX.h> 0015 #include <clang/AST/Stmt.h> 0016 #include <clang/ASTMatchers/ASTMatchFinder.h> 0017 #include <clang/ASTMatchers/ASTMatchers.h> 0018 #include <clang/ASTMatchers/ASTMatchersInternal.h> 0019 #include <clang/Basic/LLVM.h> 0020 #include <llvm/ADT/StringRef.h> 0021 #include <llvm/Support/Casting.h> 0022 0023 class ClazyContext; 0024 0025 using namespace clang; 0026 using namespace clang::ast_matchers; 0027 0028 static bool isSingleDigitRgb(llvm::StringRef ref) 0029 { 0030 return ref.size() == 4; 0031 } 0032 static bool isDoubleDigitRgb(llvm::StringRef ref) 0033 { 0034 return ref.size() == 7; 0035 } 0036 static bool isDoubleDigitRgba(llvm::StringRef ref) 0037 { 0038 return ref.size() == 9; 0039 } 0040 static bool isTripleDigitRgb(llvm::StringRef ref) 0041 { 0042 return ref.size() == 10; 0043 } 0044 static bool isQuadrupleDigitRgb(llvm::StringRef ref) 0045 { 0046 return ref.size() == 13; 0047 } 0048 0049 static bool isStringColorLiteralPattern(StringRef str) 0050 { 0051 if (!str.startswith("#")) { 0052 return false; 0053 } 0054 return isSingleDigitRgb(str) || isDoubleDigitRgb(str) || isDoubleDigitRgba(str) || isTripleDigitRgb(str) || isQuadrupleDigitRgb(str); 0055 } 0056 0057 class QColorFromLiteral_Callback : public ClazyAstMatcherCallback 0058 { 0059 public: 0060 using ClazyAstMatcherCallback::ClazyAstMatcherCallback; 0061 0062 void run(const MatchFinder::MatchResult &result) override 0063 { 0064 auto *lt = result.Nodes.getNodeAs<StringLiteral>("myLiteral"); 0065 const Expr *replaceExpr = lt; // When QColor::fromString is used, we want to wrap the QColor constructor around it 0066 bool isStaticFromString = false; 0067 if (auto res = result.Nodes.getNodeAs<CallExpr>("methodCall"); res && res->getNumArgs() == 1) { 0068 if (Expr *argExpr = const_cast<Expr *>(res->getArg(0))) { 0069 lt = clazy::getFirstChildOfType<StringLiteral>(argExpr); 0070 replaceExpr = res; 0071 isStaticFromString = true; 0072 } 0073 } 0074 if (!lt) { 0075 return; 0076 } 0077 0078 llvm::StringRef str = lt->getString(); 0079 if (!str.startswith("#")) { 0080 return; 0081 } 0082 0083 const bool singleDigit = isSingleDigitRgb(str); 0084 const bool doubleDigit = isDoubleDigitRgb(str); 0085 const bool doubleDigitA = isDoubleDigitRgba(str); 0086 if (bool isAnyValidPattern = singleDigit || doubleDigit || doubleDigitA || isTripleDigitRgb(str) || isQuadrupleDigitRgb(str); !isAnyValidPattern) { 0087 m_check->emitWarning(replaceExpr->getBeginLoc(), "Pattern length does not match any supported one by QColor, check the documentation"); 0088 return; 0089 } 0090 0091 for (unsigned int i = 1; i < str.size(); ++i) { 0092 if (!isxdigit(str[i])) { 0093 m_check->emitWarning(replaceExpr->getBeginLoc(), "QColor pattern may only contain hexadecimal digits"); 0094 return; 0095 } 0096 } 0097 0098 if (singleDigit || doubleDigit || doubleDigitA) { 0099 const int increment = singleDigit ? 1 : 2; 0100 int endPos = 1; 0101 int startPos = 1; 0102 0103 std::string aColor = doubleDigitA ? getHexValue(str, startPos, endPos, increment) : ""; 0104 std::string rColor = getHexValue(str, startPos, endPos, increment); 0105 std::string gColor = getHexValue(str, startPos, endPos, increment); 0106 std::string bColor = getHexValue(str, startPos, endPos, increment); 0107 0108 std::string fixit; 0109 std::string message; 0110 if (doubleDigitA) { 0111 const static std::string sep = ", "; 0112 fixit = prefixHex(rColor) + sep + prefixHex(gColor) + sep + prefixHex(bColor) + sep + prefixHex(aColor); 0113 message = "The QColor ctor taking ints is cheaper than one taking string literals"; 0114 } else { 0115 fixit = "0x" + twoDigit(rColor) + twoDigit(gColor) + twoDigit(bColor); 0116 message = "The QColor ctor taking RGB int value is cheaper than one taking string literals"; 0117 } 0118 if (isStaticFromString) { 0119 fixit = "QColor(" + fixit + ")"; 0120 } 0121 m_check->emitWarning(replaceExpr->getBeginLoc(), message, {clang::FixItHint::CreateReplacement(replaceExpr->getSourceRange(), fixit)}); 0122 } else { 0123 // triple or quadruple digit RGBA 0124 m_check->emitWarning(replaceExpr->getBeginLoc(), "The QColor ctor taking QRgba64 is cheaper than one taking string literals"); 0125 } 0126 } 0127 inline std::string twoDigit(const std::string &in) 0128 { 0129 return in.length() == 1 ? in + in : in; 0130 } 0131 inline std::string prefixHex(const std::string &in) 0132 { 0133 const static std::string hex = "0x"; 0134 return in == "0" ? in : hex + in; 0135 } 0136 inline std::string getHexValue(StringRef fullStr, int &startPos, int &endPos, int increment) const 0137 { 0138 endPos += increment; 0139 clang::StringRef color = fullStr.slice(startPos, endPos); 0140 startPos = endPos; 0141 0142 int result = 0; 0143 color.getAsInteger(16, result); 0144 if (result == 0) { 0145 return "0"; 0146 } else { 0147 return color.str(); 0148 } 0149 } 0150 }; 0151 0152 QColorFromLiteral::QColorFromLiteral(const std::string &name, ClazyContext *context) 0153 : CheckBase(name, context, Option_CanIgnoreIncludes) 0154 , m_astMatcherCallBack(new QColorFromLiteral_Callback(this)) 0155 { 0156 } 0157 0158 QColorFromLiteral::~QColorFromLiteral() 0159 { 0160 delete m_astMatcherCallBack; 0161 } 0162 0163 void QColorFromLiteral::VisitStmt(Stmt *stmt) 0164 { 0165 auto *call = dyn_cast<CXXMemberCallExpr>(stmt); 0166 if (!call || call->getNumArgs() != 1) { 0167 return; 0168 } 0169 0170 std::string name = clazy::qualifiedMethodName(call); 0171 if (name != "QColor::setNamedColor") { 0172 return; 0173 } 0174 0175 auto *lt = clazy::getFirstChildOfType2<StringLiteral>(call->getArg(0)); 0176 if (lt && isStringColorLiteralPattern(lt->getString())) { 0177 emitWarning(lt, "The ctor taking ints is cheaper than QColor::setNamedColor(QString)"); 0178 } 0179 } 0180 0181 void QColorFromLiteral::registerASTMatchers(MatchFinder &finder) 0182 { 0183 finder.addMatcher(cxxConstructExpr(hasDeclaration(namedDecl(hasName("QColor"))), hasArgument(0, stringLiteral().bind("myLiteral"))), m_astMatcherCallBack); 0184 finder.addMatcher(callExpr(hasDeclaration(cxxMethodDecl(hasName("fromString"), hasParent(cxxRecordDecl(hasName("QColor")))))).bind("methodCall"), 0185 m_astMatcherCallBack); 0186 }