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 }