File indexing completed on 2024-04-21 05:38:45

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