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 }