File indexing completed on 2024-04-28 16:57:48

0001 /*
0002   This file is part of the clazy static checker.
0003 
0004     Copyright (C) 2016 Sergio Martins <smartins@kde.org>
0005     Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0006 
0007     This library is free software; you can redistribute it and/or
0008     modify it under the terms of the GNU Library General Public
0009     License as published by the Free Software Foundation; either
0010     version 2 of the License, or (at your option) any later version.
0011 
0012     This library is distributed in the hope that it will be useful,
0013     but WITHOUT ANY WARRANTY; without even the implied warranty of
0014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015     Library General Public License for more details.
0016 
0017     You should have received a copy of the GNU Library General Public License
0018     along with this library; see the file COPYING.LIB.  If not, write to
0019     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020     Boston, MA 02110-1301, USA.
0021 */
0022 
0023 #include "AccessSpecifierManager.h"
0024 #include "QtUtils.h"
0025 #include "SourceCompatibilityHelpers.h"
0026 #include "Utils.h"
0027 #include "ClazyContext.h"
0028 
0029 #include <clang/Basic/SourceManager.h>
0030 #include <clang/AST/DeclCXX.h>
0031 #include <clang/AST/DeclBase.h>
0032 #include <clang/AST/DeclTemplate.h>
0033 #include <clang/Basic/IdentifierTable.h>
0034 #include <clang/Basic/LLVM.h>
0035 #include <clang/Frontend/CompilerInstance.h>
0036 #include <clang/Lex/PPCallbacks.h>
0037 #include <clang/Lex/Preprocessor.h>
0038 #include <clang/Lex/Token.h>
0039 #include <llvm/Support/Casting.h>
0040 
0041 #include <algorithm>
0042 #include <memory>
0043 #include <utility>
0044 
0045 namespace clang {
0046 class MacroArgs;
0047 class MacroDefinition;
0048 }  // namespace clang
0049 
0050 using namespace clang;
0051 using namespace std;
0052 
0053 static bool accessSpecifierCompare(const ClazyAccessSpecifier &lhs, const ClazyAccessSpecifier &rhs,
0054                                    const SourceManager &sm)
0055 {
0056     if (lhs.loc.isMacroID() || rhs.loc.isMacroID()) {
0057         // Q_SIGNALS is special because it hides a "public", which is expanded by this macro.
0058         // That means that both the Q_SIGNALS macro and the "public" will have the same source location.
0059         // We do want the "public" to appear before, so check if one has a macro id on it.
0060 
0061         SourceLocation realLHSLoc = sm.getFileLoc(lhs.loc);
0062         SourceLocation realRHSLoc = sm.getFileLoc(rhs.loc);
0063         if (realLHSLoc == realRHSLoc) {
0064             return lhs.loc.isMacroID();
0065         } else {
0066             return realLHSLoc < realRHSLoc;
0067         }
0068     }
0069 
0070     return lhs.loc < rhs.loc;
0071 }
0072 
0073 static void sorted_insert(ClazySpecifierList &v, const ClazyAccessSpecifier &item, const clang::SourceManager &sm)
0074 {
0075     auto pred = [&sm] (const ClazyAccessSpecifier &lhs, const ClazyAccessSpecifier &rhs) {
0076         return accessSpecifierCompare(lhs, rhs, sm);
0077     };
0078     v.insert(std::upper_bound(v.begin(), v.end(), item, pred), item);
0079 }
0080 
0081 class AccessSpecifierPreprocessorCallbacks
0082     : public clang::PPCallbacks
0083 {
0084     AccessSpecifierPreprocessorCallbacks(const AccessSpecifierPreprocessorCallbacks &) = delete;
0085 public:
0086     AccessSpecifierPreprocessorCallbacks(const clang::CompilerInstance &ci)
0087         : clang::PPCallbacks()
0088         , m_ci(ci)
0089     {
0090         m_qtAccessSpecifiers.reserve(30); // bootstrap it
0091     }
0092 
0093     void MacroExpands(const Token &MacroNameTok, const MacroDefinition &,
0094                       SourceRange range, const MacroArgs *) override
0095     {
0096         IdentifierInfo *ii = MacroNameTok.getIdentifierInfo();
0097         if (!ii)
0098             return;
0099 
0100         auto name = ii->getName();
0101         const bool isSlots = name == "slots" || name == "Q_SLOTS";
0102         const bool isSignals = isSlots ? false : (name == "signals" || name == "Q_SIGNALS");
0103 
0104         const bool isSlot = (isSlots || isSignals) ? false : name == "Q_SLOT";
0105         const bool isSignal = (isSlots || isSignals || isSlot) ? false : name == "Q_SIGNAL";
0106         const bool isInvokable = (isSlots || isSignals || isSlot || isSignal) ? false : name == "Q_INVOKABLE";
0107         const bool isScriptable = (isSlot || isSignal || isInvokable) ? false : name == "Q_SCRIPTABLE";
0108         if (!isSlots && !isSignals && !isSlot && !isSignal && !isInvokable && !isScriptable)
0109             return;
0110 
0111         SourceLocation loc = range.getBegin();
0112         if (loc.isMacroID())
0113             return;
0114 
0115         if (isSignals || isSlots) {
0116             QtAccessSpecifierType qtAccessSpecifier = isSlots ? QtAccessSpecifier_Slot
0117                                                               : QtAccessSpecifier_Signal;
0118             m_qtAccessSpecifiers.push_back( { loc, clang::AS_none, qtAccessSpecifier } );
0119         } else {
0120             // Get the location of the method declaration, so we can compare directly when we visit methods
0121             loc = Utils::locForNextToken(loc, m_ci.getSourceManager(), m_ci.getLangOpts());
0122             if (loc.isInvalid())
0123                 return;
0124             if (isSignal) {
0125                 m_individualSignals.push_back(loc.getRawEncoding());
0126             } else if (isSlot) {
0127                 m_individualSlots.push_back(loc.getRawEncoding());
0128             } else if (isInvokable) {
0129                 m_invokables.push_back(loc.getRawEncoding());
0130             } else if (isScriptable) {
0131                 m_scriptables.push_back(loc.getRawEncoding());
0132             }
0133         }
0134     }
0135 
0136     vector<unsigned> m_individualSignals; // Q_SIGNAL
0137     vector<unsigned> m_individualSlots;   // Q_SLOT
0138     vector<unsigned> m_invokables; // Q_INVOKABLE
0139     vector<unsigned> m_scriptables; // Q_SCRIPTABLE
0140     const CompilerInstance &m_ci;
0141     ClazySpecifierList m_qtAccessSpecifiers;
0142 };
0143 
0144 AccessSpecifierManager::AccessSpecifierManager(ClazyContext *context)
0145     : m_ci(context->ci)
0146     , m_preprocessorCallbacks(new AccessSpecifierPreprocessorCallbacks(m_ci))
0147     , m_fixitsEnabled(context->exportFixesEnabled())
0148 {
0149     Preprocessor &pi = m_ci.getPreprocessor();
0150     pi.addPPCallbacks(unique_ptr<PPCallbacks>(m_preprocessorCallbacks));
0151     m_visitsNonQObjects = getenv("CLAZY_ACCESSSPECIFIER_NON_QOBJECT") != nullptr;
0152 }
0153 
0154 ClazySpecifierList& AccessSpecifierManager::entryForClassDefinition(CXXRecordDecl *classDecl)
0155 {
0156     ClazySpecifierList &specifiers = m_specifiersMap[classDecl];
0157     return specifiers;
0158 }
0159 
0160 const CXXRecordDecl *AccessSpecifierManager::classDefinitionForLoc(SourceLocation loc) const
0161 {
0162     for (const auto &it : m_specifiersMap) {
0163         const CXXRecordDecl *record = it.first;
0164         if (clazy::getLocStart(record) < loc && loc < clazy::getLocEnd(record))
0165             return record;
0166     }
0167     return nullptr;
0168 }
0169 
0170 void AccessSpecifierManager::VisitDeclaration(Decl *decl)
0171 {
0172     auto record = dyn_cast<CXXRecordDecl>(decl);
0173     if (!record)
0174         return;
0175 
0176     const bool isQObject = clazy::isQObject(record);
0177     const bool visits = isQObject || (m_fixitsEnabled && m_visitsNonQObjects);
0178 
0179     if (!visits) {
0180         // We're only interested if it's a QObject. Otherwise it's a waste of cpu cycles,
0181         // causing a big slowdown. However, the copyable-polymorphic fixit needs to know the locations
0182         // of "private:", so allow that too.
0183 
0184         // The copyable-polymorphic fixit is opt-in, via an env variable CLAZY_ACCESSSPECIFIER_NON_QOBJECT
0185         // as it's buggy
0186         return;
0187     }
0188 
0189     const auto &sm = m_ci.getSourceManager();
0190 
0191     // We got a new record, lets fetch signals and slots that the pre-processor gathered
0192     ClazySpecifierList &specifiers = entryForClassDefinition(record);
0193 
0194     auto it = m_preprocessorCallbacks->m_qtAccessSpecifiers.begin();
0195     while (it != m_preprocessorCallbacks->m_qtAccessSpecifiers.end()) {
0196          if (classDefinitionForLoc((*it).loc) == record) {
0197             sorted_insert(specifiers, *it, sm);
0198             it = m_preprocessorCallbacks->m_qtAccessSpecifiers.erase(it);
0199         } else {
0200             ++it;
0201         }
0202     }
0203 
0204     // Now lets add the normal C++ access specifiers (public, private etc.)
0205 
0206     for (auto d : record->decls()) {
0207         auto accessSpec = dyn_cast<AccessSpecDecl>(d);
0208         if (!accessSpec || accessSpec->getDeclContext() != record)
0209             continue;
0210         ClazySpecifierList &specifiers = entryForClassDefinition(record);
0211         sorted_insert(specifiers, {clazy::getLocStart(accessSpec), accessSpec->getAccess(), QtAccessSpecifier_None }, sm);
0212     }
0213 }
0214 
0215 QtAccessSpecifierType AccessSpecifierManager::qtAccessSpecifierType(const CXXMethodDecl *method) const
0216 {
0217     if (!method || clazy::getLocStart(method).isMacroID())
0218         return QtAccessSpecifier_Unknown;
0219 
0220     // We want the declaration that's inside class {}, not the ones that are also a method definition
0221     // and possibly outside the class
0222     method = method->getCanonicalDecl();
0223 
0224     const CXXRecordDecl *record = method->getParent();
0225     if (!record || isa<clang::ClassTemplateSpecializationDecl>(record) || method->isTemplateInstantiation())
0226         return QtAccessSpecifier_None;
0227 
0228     const SourceLocation methodLoc = clazy::getLocStart(method);
0229 
0230     // Process Q_SIGNAL:
0231     for (auto signalLoc : m_preprocessorCallbacks->m_individualSignals) {
0232         if (signalLoc == methodLoc.getRawEncoding())
0233             return QtAccessSpecifier_Signal;
0234     }
0235 
0236     // Process Q_SLOT:
0237     for (auto slotLoc : m_preprocessorCallbacks->m_individualSlots) {
0238         if (slotLoc == methodLoc.getRawEncoding())
0239             return QtAccessSpecifier_Slot;
0240     }
0241 
0242     // Process Q_INVOKABLE:
0243     for (auto loc : m_preprocessorCallbacks->m_invokables) {
0244         if (loc == methodLoc.getRawEncoding())
0245             return QtAccessSpecifier_Invokable;
0246     }
0247 
0248     // Process Q_SLOTS and Q_SIGNALS:
0249 
0250     auto it = m_specifiersMap.find(record);
0251     if (it == m_specifiersMap.cend())
0252         return QtAccessSpecifier_None;
0253 
0254     const ClazySpecifierList &accessSpecifiers = it->second;
0255 
0256     auto pred = [this] (const ClazyAccessSpecifier &lhs, const ClazyAccessSpecifier &rhs) {
0257         return accessSpecifierCompare(lhs, rhs, m_ci.getSourceManager());
0258     };
0259 
0260     const ClazyAccessSpecifier dummy = { methodLoc, // we're only interested in the location
0261                                          /*dummy*/ clang::AS_none,
0262                                          /*dummy*/ QtAccessSpecifier_None };
0263     auto i = std::upper_bound(accessSpecifiers.cbegin(), accessSpecifiers.cend(), dummy, pred);
0264     if (i == accessSpecifiers.cbegin())
0265         return QtAccessSpecifier_None;
0266 
0267 
0268     --i; // One before the upper bound is the last access specifier before our method
0269     return (*i).qtAccessSpecifier;
0270 }
0271 
0272 bool AccessSpecifierManager::isScriptable(const CXXMethodDecl *method) const
0273 {
0274     if (!method)
0275         return false;
0276 
0277     const SourceLocation methodLoc = clazy::getLocStart(method);
0278      if (methodLoc.isMacroID())
0279          return false;
0280 
0281     for (auto loc : m_preprocessorCallbacks->m_scriptables) {
0282         if (loc == methodLoc.getRawEncoding())
0283             return true;
0284     }
0285 
0286     return false;
0287 }
0288 
0289 llvm::StringRef AccessSpecifierManager::qtAccessSpecifierTypeStr(QtAccessSpecifierType t) const
0290 {
0291     switch (t) {
0292     case QtAccessSpecifier_None:
0293     case QtAccessSpecifier_Unknown:
0294         return "";
0295     case QtAccessSpecifier_Slot:
0296         return "slot";
0297     case QtAccessSpecifier_Signal:
0298         return "signal";
0299     case QtAccessSpecifier_Invokable:
0300         return "invokable";
0301     }
0302 
0303     return "";
0304 }
0305 
0306 SourceLocation AccessSpecifierManager::firstLocationOfSection(
0307     AccessSpecifier specifier, clang::CXXRecordDecl *decl) const {
0308 
0309     auto it = m_specifiersMap.find(decl);
0310     if (it == m_specifiersMap.end())
0311         return {};
0312 
0313     for (const auto &record : it->second) {
0314         if (record.accessSpecifier == specifier) {
0315             return record.loc;
0316         }
0317     }
0318     return {};
0319 }