File indexing completed on 2024-05-12 05:40:58

0001 /*
0002     SPDX-FileCopyrightText: 2016 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "incorrect-emit.h"
0008 #include "AccessSpecifierManager.h"
0009 #include "ClazyContext.h"
0010 #include "HierarchyUtils.h"
0011 #include "Utils.h"
0012 
0013 #include <clang/AST/DeclCXX.h>
0014 #include <clang/AST/Expr.h>
0015 #include <clang/AST/ExprCXX.h>
0016 #include <clang/AST/Stmt.h>
0017 #include <clang/Basic/IdentifierTable.h>
0018 #include <clang/Basic/LLVM.h>
0019 #include <clang/Basic/SourceManager.h>
0020 #include <clang/Lex/Token.h>
0021 #include <llvm/ADT/StringRef.h>
0022 #include <llvm/Support/Casting.h>
0023 
0024 #include <utility>
0025 
0026 namespace clang
0027 {
0028 class MacroInfo;
0029 } // namespace clang
0030 
0031 using namespace clang;
0032 
0033 IncorrectEmit::IncorrectEmit(const std::string &name, ClazyContext *context)
0034     : CheckBase(name, context, Option_CanIgnoreIncludes)
0035 {
0036     context->enableAccessSpecifierManager();
0037     enablePreProcessorCallbacks();
0038     m_emitLocations.reserve(30); // bootstrap it
0039     m_filesToIgnore = {"moc_", ".moc"};
0040 }
0041 
0042 void IncorrectEmit::VisitMacroExpands(const Token &MacroNameTok, const SourceRange &range, const MacroInfo *)
0043 {
0044     IdentifierInfo *ii = MacroNameTok.getIdentifierInfo();
0045     if (ii && (ii->getName() == "emit" || ii->getName() == "Q_EMIT")) {
0046         m_emitLocations.push_back(range.getBegin());
0047     }
0048 }
0049 
0050 void IncorrectEmit::VisitStmt(Stmt *stmt)
0051 {
0052     auto *methodCall = dyn_cast<CXXMemberCallExpr>(stmt);
0053     if (!methodCall || !methodCall->getCalleeDecl()) {
0054         return;
0055     }
0056 
0057     AccessSpecifierManager *accessSpecifierManager = m_context->accessSpecifierManager;
0058     auto *method = dyn_cast<CXXMethodDecl>(methodCall->getCalleeDecl());
0059     if (!method || !accessSpecifierManager) {
0060         return;
0061     }
0062 
0063     if (shouldIgnoreFile(stmt->getBeginLoc())) {
0064         return;
0065     }
0066 
0067     if (Stmt *parent = clazy::parent(m_context->parentMap, methodCall)) {
0068         // Check if we're inside a chained call, such as: emit d_func()->mySignal()
0069         // We're not interested in the d_func() call, so skip it
0070         if (clazy::getFirstParentOfType<CXXMemberCallExpr>(m_context->parentMap, parent)) {
0071             return;
0072         }
0073     }
0074 
0075     const QtAccessSpecifierType type = accessSpecifierManager->qtAccessSpecifierType(method);
0076     if (type == QtAccessSpecifier_Unknown) {
0077         return;
0078     }
0079 
0080     const bool hasEmit = hasEmitKeyboard(methodCall);
0081     const std::string methodName = method->getQualifiedNameAsString();
0082     const bool isSignal = type == QtAccessSpecifier_Signal;
0083     if (isSignal && !hasEmit) {
0084         emitWarning(stmt, "Missing emit keyword on signal call " + methodName);
0085     } else if (!isSignal && hasEmit) {
0086         emitWarning(stmt, "Emit keyword being used with non-signal " + methodName);
0087     }
0088 
0089     if (isSignal) {
0090         checkCallSignalInsideCTOR(methodCall);
0091     }
0092 }
0093 
0094 void IncorrectEmit::checkCallSignalInsideCTOR(CXXMemberCallExpr *callExpr)
0095 {
0096     if (!m_context->lastMethodDecl) {
0097         return;
0098     }
0099 
0100     auto *ctorDecl = dyn_cast<CXXConstructorDecl>(m_context->lastMethodDecl);
0101     if (!ctorDecl) {
0102         return;
0103     }
0104 
0105     Expr *implicitArg = callExpr->getImplicitObjectArgument();
0106     if (!implicitArg || !isa<CXXThisExpr>(implicitArg)) { // emit other->sig() is ok
0107         return;
0108     }
0109 
0110     if (clazy::getFirstParentOfType<LambdaExpr>(m_context->parentMap, callExpr) != nullptr) {
0111         return; // Emit is inside a lambda, it's fine
0112     }
0113 
0114     emitWarning(callExpr->getBeginLoc(), "Emitting inside constructor probably has no effect");
0115 }
0116 
0117 bool IncorrectEmit::hasEmitKeyboard(CXXMemberCallExpr *call) const
0118 {
0119     SourceLocation callLoc = call->getBeginLoc();
0120     if (callLoc.isMacroID()) {
0121         callLoc = sm().getFileLoc(callLoc);
0122     }
0123 
0124     for (const SourceLocation &emitLoc : m_emitLocations) {
0125         // We cache the calculation of the next token because it uses the Lexer and hence expensive.
0126         auto it = m_locationCache.find(emitLoc.getRawEncoding());
0127 
0128         SourceLocation nextTokenLoc;
0129         if (it == m_locationCache.end()) {
0130             nextTokenLoc = Utils::locForNextToken(emitLoc, sm(), lo());
0131             m_locationCache[emitLoc.getRawEncoding()] = nextTokenLoc;
0132         } else {
0133             nextTokenLoc = it->second;
0134         }
0135 
0136         if (nextTokenLoc == callLoc) {
0137             return true;
0138         }
0139     }
0140 
0141     return false;
0142 }