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

0001 /*
0002     SPDX-FileCopyrightText: 2020 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "use-chrono-in-qtimer.h"
0008 #include "ClazyContext.h"
0009 #include "FixItUtils.h"
0010 #include "HierarchyUtils.h"
0011 #include "PreProcessorVisitor.h"
0012 #include <clang/AST/AST.h>
0013 
0014 using namespace clang;
0015 
0016 UseChronoInQTimer::UseChronoInQTimer(const std::string &name, ClazyContext *context)
0017     : CheckBase(name, context, Option_CanIgnoreIncludes)
0018 {
0019     context->enablePreprocessorVisitor();
0020 }
0021 
0022 static int unpackValue(clang::Expr *expr)
0023 {
0024     auto *value = dyn_cast<IntegerLiteral>(expr);
0025     if (value) {
0026         return static_cast<int>(*value->getValue().getRawData());
0027     }
0028 
0029     auto *binaryOp = dyn_cast<BinaryOperator>(expr);
0030     if (!binaryOp) {
0031         return -1;
0032     }
0033 
0034     int left = unpackValue(binaryOp->getLHS());
0035     int right = unpackValue(binaryOp->getRHS());
0036     if (left == -1 || right == -1) {
0037         return -1;
0038     }
0039 
0040     if (binaryOp->getOpcode() == BO_Mul) {
0041         return left * right;
0042     }
0043 
0044     if (binaryOp->getOpcode() == BO_Div) {
0045         return left / right;
0046     }
0047 
0048     return -1;
0049 }
0050 
0051 void UseChronoInQTimer::warn(const clang::Stmt *stmt, int value)
0052 {
0053     if (value == 0) {
0054         return; // ignore zero times;
0055     }
0056 
0057     std::string suggestion;
0058     if (value % (1000 * 3600) == 0) {
0059         suggestion = std::to_string(value / 1000 / 3600) + "h";
0060     } else if (value % (1000 * 60) == 0) {
0061         suggestion = std::to_string(value / 1000 / 60) + "min";
0062     } else if (value % 1000 == 0) {
0063         suggestion = std::to_string(value / 1000) + "s";
0064     } else {
0065         suggestion = std::to_string(value) + "ms";
0066     }
0067 
0068     std::vector<FixItHint> fixits;
0069     fixits.push_back(FixItHint::CreateReplacement(stmt->getSourceRange(), suggestion));
0070 
0071     if (!m_hasInsertedInclude && !m_context->preprocessorVisitor->hasInclude("chrono", true)) {
0072         fixits.push_back(clazy::createInsertion(m_context->preprocessorVisitor->endOfIncludeSection(),
0073                                                 "\n"
0074                                                 "#include <chrono>\n\n"
0075                                                 "using namespace std::chrono_literals;"));
0076     }
0077     m_hasInsertedInclude = true;
0078 
0079     emitWarning(stmt->getBeginLoc(), "make code more robust: use " + suggestion + " instead.", fixits);
0080 }
0081 
0082 static std::string functionName(CallExpr *callExpr)
0083 {
0084     auto *memberCall = clazy::getFirstChildOfType<MemberExpr>(callExpr);
0085     if (memberCall) {
0086         auto *methodDecl = dyn_cast<CXXMethodDecl>(memberCall->getMemberDecl());
0087         if (!methodDecl) {
0088             return {};
0089         }
0090         return methodDecl->getQualifiedNameAsString();
0091     }
0092 
0093     FunctionDecl *fdecl = callExpr->getDirectCallee();
0094     if (fdecl) {
0095         return fdecl->getQualifiedNameAsString();
0096     }
0097 
0098     return {};
0099 }
0100 
0101 void UseChronoInQTimer::VisitStmt(clang::Stmt *stmt)
0102 {
0103     auto *callExpr = dyn_cast<CallExpr>(stmt);
0104     if (!callExpr) {
0105         return;
0106     }
0107 
0108     if (callExpr->getNumArgs() == 0) {
0109         return; // start() doesn't take any arguments.
0110     }
0111 
0112     const std::string name = functionName(callExpr);
0113     if (name != "QTimer::setInterval" && name != "QTimer::start" && name != "QTimer::singleShot") {
0114         return;
0115     }
0116 
0117     const int value = unpackValue(callExpr->getArg(0));
0118     if (value == -1) {
0119         return;
0120     }
0121 
0122     warn(callExpr->getArg(0), value);
0123 }