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 }