File indexing completed on 2024-05-12 04:38:03

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "persistentsymboltable.h"
0008 
0009 #include <QHash>
0010 
0011 #include "declaration.h"
0012 #include "declarationid.h"
0013 #include "appendedlist.h"
0014 #include "serialization/itemrepository.h"
0015 #include "identifier.h"
0016 #include "ducontext.h"
0017 #include "topducontext.h"
0018 #include "duchain.h"
0019 #include "duchainlock.h"
0020 
0021 #include <util/convenientfreelist.h>
0022 #include <util/embeddedfreetree.h>
0023 
0024 #include <language/util/setrepository.h>
0025 
0026 #if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS)
0027 #define VERIFY_VISIT_NESTING 0
0028 #define ifVerifyVisitNesting(x)
0029 #else
0030 #define VERIFY_VISIT_NESTING 1
0031 #define ifVerifyVisitNesting(x) x
0032 #endif
0033 
0034 namespace KDevelop {
0035 
0036 namespace {
0037 // For now, just _always_ use the cache
0038 const uint MinimumCountForCache = 1;
0039 
0040 QDebug fromTextStream(const QTextStream& out)
0041 {
0042     if (out.device())
0043         return {out.device()};
0044     return {out.string()};
0045 }
0046 
0047 struct IndexedDeclarationHandler {
0048     inline static int leftChild(const IndexedDeclaration& m_data) { return ((int)(m_data.dummyData().first)) - 1; }
0049     inline static void setLeftChild(IndexedDeclaration& m_data, int child)
0050     {
0051         m_data.setDummyData(qMakePair((uint)(child + 1), m_data.dummyData().second));
0052     }
0053     inline static int rightChild(const IndexedDeclaration& m_data) { return ((int)m_data.dummyData().second) - 1; }
0054     inline static void setRightChild(IndexedDeclaration& m_data, int child)
0055     {
0056         m_data.setDummyData(qMakePair(m_data.dummyData().first, (uint)(child + 1)));
0057     }
0058     inline static void createFreeItem(IndexedDeclaration& data)
0059     {
0060         data = IndexedDeclaration();
0061         data.setIsDummy(true);
0062         data.setDummyData(qMakePair(0u, 0u)); // Since we subtract 1, this equals children -1, -1
0063     }
0064     // Copies this item into the given one
0065     inline static void copyTo(const IndexedDeclaration& m_data, IndexedDeclaration& data) { data = m_data; }
0066 
0067     inline static bool isFree(const IndexedDeclaration& m_data) { return m_data.isDummy(); }
0068 
0069     inline static bool equals(const IndexedDeclaration& m_data, const IndexedDeclaration& rhs) { return m_data == rhs; }
0070 };
0071 
0072 struct DeclarationTopContextExtractor {
0073     inline static IndexedTopDUContext extract(const IndexedDeclaration& decl) { return decl.indexedTopContext(); }
0074 };
0075 
0076 struct DUContextTopContextExtractor {
0077     inline static IndexedTopDUContext extract(const IndexedDUContext& ctx) { return ctx.indexedTopContext(); }
0078 };
0079 
0080 struct RecursiveImportCacheRepository {
0081     static Utils::BasicSetRepository* repository()
0082     {
0083         static QRecursiveMutex mutex;
0084         static Utils::BasicSetRepository recursiveImportCacheRepositoryObject(QStringLiteral("Recursive Imports Cache"),
0085                                                                               &mutex, nullptr, false);
0086         return &recursiveImportCacheRepositoryObject;
0087     }
0088 };
0089 
0090 DEFINE_LIST_MEMBER_HASH(PersistentSymbolTableItem, declarations, IndexedDeclaration)
0091 
0092 class PersistentSymbolTableItem
0093 {
0094 public:
0095     PersistentSymbolTableItem() : centralFreeItem(-1)
0096     {
0097         initializeAppendedLists();
0098     }
0099     PersistentSymbolTableItem(const PersistentSymbolTableItem& rhs, bool dynamic = true) : id(rhs.id)
0100         , centralFreeItem(rhs.centralFreeItem)
0101     {
0102         initializeAppendedLists(dynamic);
0103         copyListsFrom(rhs);
0104     }
0105 
0106     ~PersistentSymbolTableItem()
0107     {
0108         freeAppendedLists();
0109     }
0110 
0111     PersistentSymbolTableItem& operator=(const PersistentSymbolTableItem& rhs) = delete;
0112 
0113     inline unsigned int hash() const
0114     {
0115         //We only compare the declaration. This allows us implementing a map, although the item-repository
0116         //originally represents a set.
0117         return id.index();
0118     }
0119 
0120     unsigned int itemSize() const
0121     {
0122         return dynamicSize();
0123     }
0124 
0125     uint classSize() const
0126     {
0127         return sizeof(PersistentSymbolTableItem);
0128     }
0129 
0130     IndexedQualifiedIdentifier id;
0131     int centralFreeItem;
0132 
0133     START_APPENDED_LISTS(PersistentSymbolTableItem);
0134     APPENDED_LIST_FIRST(PersistentSymbolTableItem, IndexedDeclaration, declarations);
0135     END_APPENDED_LISTS(PersistentSymbolTableItem, declarations);
0136 };
0137 
0138 class PersistentSymbolTableRequestItem
0139 {
0140 public:
0141 
0142     PersistentSymbolTableRequestItem(const PersistentSymbolTableItem& item) : m_item(item)
0143     {
0144     }
0145     enum {
0146         AverageSize = 30 //This should be the approximate average size of an Item
0147     };
0148 
0149     unsigned int hash() const
0150     {
0151         return m_item.hash();
0152     }
0153 
0154     uint itemSize() const
0155     {
0156         return m_item.itemSize();
0157     }
0158 
0159     void createItem(PersistentSymbolTableItem* item) const
0160     {
0161         new (item) PersistentSymbolTableItem(m_item, false);
0162     }
0163 
0164     static void destroy(PersistentSymbolTableItem* item, KDevelop::AbstractItemRepository&)
0165     {
0166         item->~PersistentSymbolTableItem();
0167     }
0168 
0169     static bool persistent(const PersistentSymbolTableItem*)
0170     {
0171         return true; //Nothing to do
0172     }
0173 
0174     bool equals(const PersistentSymbolTableItem* item) const
0175     {
0176         return m_item.id == item->id;
0177     }
0178 
0179     const PersistentSymbolTableItem& m_item;
0180 };
0181 
0182 using CachedDeclarations = KDevVarLengthArray<IndexedDeclaration>;
0183 using CachedDeclarationsByImports = QHash<TopDUContext::IndexedRecursiveImports, CachedDeclarations>;
0184 using Declarations = ConstantConvenientEmbeddedSet<IndexedDeclaration, IndexedDeclarationHandler>;
0185 using CachedIndexedRecursiveImports =
0186     Utils::StorableSet<IndexedTopDUContext, IndexedTopDUContextIndexConversion, RecursiveImportCacheRepository, true>;
0187 using FilteredDeclarationIterator =
0188     ConvenientEmbeddedSetTreeFilterIterator<IndexedDeclaration, IndexedDeclarationHandler, IndexedTopDUContext,
0189                                             CachedIndexedRecursiveImports, DeclarationTopContextExtractor>;
0190 
0191 // Maps declaration-ids to declarations, together with some caches
0192 class PersistentSymbolTableRepo
0193     : public ItemRepository<PersistentSymbolTableItem, PersistentSymbolTableRequestItem, true, QRecursiveMutex>
0194 {
0195     using ItemRepository::ItemRepository;
0196 
0197 public:
0198     QHash<IndexedQualifiedIdentifier, CachedDeclarationsByImports> declarationsCache;
0199 
0200     // We cache the imports so the currently used nodes are very close in memory, which leads to much better CPU cache
0201     // utilization
0202     QHash<TopDUContext::IndexedRecursiveImports, CachedIndexedRecursiveImports> importsCache;
0203 
0204     /// Counts how many recursive calls to PersistentSymbolTable::visit* functions are ongoing
0205     /// and hold the repository's mutex lock. Is used only to assert correct API use.
0206     mutable uint ongoingIterations = 0;
0207 };
0208 
0209 #if VERIFY_VISIT_NESTING
0210 struct IterationCounter
0211 {
0212     explicit IterationCounter(const PersistentSymbolTableRepo& repo)
0213         : repo(repo)
0214     {
0215         ++repo.ongoingIterations;
0216     }
0217 
0218     ~IterationCounter()
0219     {
0220         --repo.ongoingIterations;
0221     }
0222 
0223 private:
0224     Q_DISABLE_COPY_MOVE(IterationCounter)
0225 
0226     const PersistentSymbolTableRepo& repo;
0227 };
0228 #endif
0229 }
0230 
0231 template<>
0232 class ItemRepositoryFor<PersistentSymbolTable>
0233 {
0234     friend struct LockedItemRepository;
0235     static PersistentSymbolTableRepo& repo()
0236     {
0237         static QRecursiveMutex mutex;
0238         static PersistentSymbolTableRepo repo { QStringLiteral("Persistent Declaration Table"), &mutex };
0239         return repo;
0240     }
0241 };
0242 
0243 void PersistentSymbolTable::clearCache()
0244 {
0245     LockedItemRepository::write<PersistentSymbolTable>([](PersistentSymbolTableRepo& repo) {
0246         Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call clearCache directly from a visitor");
0247 
0248         repo.importsCache.clear();
0249         repo.declarationsCache.clear();
0250     });
0251 }
0252 
0253 PersistentSymbolTable::PersistentSymbolTable()
0254 {
0255     // PersistentSymbolTableRepo::importsCache uses RecursiveImportCacheRepository, so the cache repository must be
0256     // destroyed after and therefore created before the persistent symbol table repository.
0257     RecursiveImportCacheRepository::repository();
0258     LockedItemRepository::initialize<PersistentSymbolTable>();
0259 }
0260 
0261 PersistentSymbolTable::~PersistentSymbolTable() = default;
0262 
0263 void PersistentSymbolTable::addDeclaration(const IndexedQualifiedIdentifier& id, const IndexedDeclaration& declaration)
0264 {
0265     ENSURE_CHAIN_WRITE_LOCKED
0266 
0267     PersistentSymbolTableItem item;
0268     item.id = id;
0269 
0270     LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) {
0271         Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call addDeclaration directly from a visitor");
0272 
0273         repo.declarationsCache.remove(item.id);
0274 
0275         uint index = repo.findIndex(item);
0276 
0277         if (index) {
0278             // Check whether the item is already in the mapped list, else copy the list into the new created item
0279             const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index);
0280 
0281             EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg(
0282                 oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem);
0283 
0284             if (alg.indexOf(declaration) != -1)
0285                 return;
0286 
0287             DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index);
0288 
0289             EmbeddedTreeAddItem<IndexedDeclaration, IndexedDeclarationHandler> add(
0290                 const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(),
0291                 editableItem->centralFreeItem, declaration);
0292 
0293             uint newSize = add.newItemCount();
0294             if (newSize != editableItem->declarationsSize()) {
0295                 // We need to resize. Update and fill the new item, and delete the old item.
0296                 item.declarationsList().resize(newSize);
0297                 add.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem);
0298 
0299                 repo.deleteItem(index);
0300                 Q_ASSERT(!repo.findIndex(item));
0301             } else {
0302                 // We're fine, the item could be added to the existing list
0303                 return;
0304             }
0305         } else {
0306             item.declarationsList().append(declaration);
0307         }
0308 
0309         // This inserts the changed item
0310         repo.index(item);
0311     });
0312 }
0313 
0314 void PersistentSymbolTable::removeDeclaration(const IndexedQualifiedIdentifier& id,
0315                                               const IndexedDeclaration& declaration)
0316 {
0317     ENSURE_CHAIN_WRITE_LOCKED
0318 
0319     PersistentSymbolTableItem item;
0320     item.id = id;
0321 
0322     LockedItemRepository::write<PersistentSymbolTable>([&item, &declaration](PersistentSymbolTableRepo& repo) {
0323         Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call removeDeclaration directly from a visitor");
0324 
0325         repo.declarationsCache.remove(item.id);
0326 
0327         uint index = repo.findIndex(item);
0328 
0329         if (index) {
0330             // Check whether the item is already in the mapped list, else copy the list into the new created item
0331             const PersistentSymbolTableItem* oldItem = repo.itemFromIndex(index);
0332 
0333             EmbeddedTreeAlgorithms<IndexedDeclaration, IndexedDeclarationHandler> alg(
0334                 oldItem->declarations(), oldItem->declarationsSize(), oldItem->centralFreeItem);
0335 
0336             if (alg.indexOf(declaration) == -1)
0337                 return;
0338 
0339             DynamicItem<PersistentSymbolTableItem, true> editableItem = repo.dynamicItemFromIndex(index);
0340 
0341             EmbeddedTreeRemoveItem<IndexedDeclaration, IndexedDeclarationHandler> remove(
0342                 const_cast<IndexedDeclaration*>(editableItem->declarations()), editableItem->declarationsSize(),
0343                 editableItem->centralFreeItem, declaration);
0344 
0345             uint newSize = remove.newItemCount();
0346             if (newSize != editableItem->declarationsSize()) {
0347                 // We need to resize. Update and fill the new item, and delete the old item.
0348                 item.declarationsList().resize(newSize);
0349                 remove.transferData(item.declarationsList().data(), newSize, &item.centralFreeItem);
0350 
0351                 repo.deleteItem(index);
0352                 Q_ASSERT(!repo.findIndex(item));
0353             } else {
0354                 // We're fine, the item could be added to the existing list
0355                 return;
0356             }
0357         }
0358 
0359         // This inserts the changed item
0360         if (item.declarationsSize())
0361             repo.index(item);
0362     });
0363 }
0364 
0365 void PersistentSymbolTable::visitDeclarations(const IndexedQualifiedIdentifier& id,
0366                                               const DeclarationVisitor& visitor) const
0367 {
0368     ENSURE_CHAIN_READ_LOCKED
0369 
0370     PersistentSymbolTableItem item;
0371     item.id = id;
0372 
0373     LockedItemRepository::read<PersistentSymbolTable>([&item, &visitor](const PersistentSymbolTableRepo& repo) {
0374         ifVerifyVisitNesting(const auto guard = IterationCounter(repo);)
0375 
0376         uint index = repo.findIndex(item);
0377 
0378         if (!index) {
0379             return;
0380         }
0381 
0382         const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index);
0383 
0384         const auto numDeclarations = repositoryItem->declarationsSize();
0385         const auto declarations = repositoryItem->declarations();
0386         for (uint i = 0; i < numDeclarations; ++i) {
0387             if (visitor(declarations[i]) == VisitorState::Break) {
0388                 break;
0389             }
0390         }
0391     });
0392 }
0393 
0394 void PersistentSymbolTable::visitFilteredDeclarations(const IndexedQualifiedIdentifier& id,
0395                                                       const TopDUContext::IndexedRecursiveImports& visibility,
0396                                                       const DeclarationVisitor& visitor) const
0397 {
0398     ENSURE_CHAIN_READ_LOCKED
0399 
0400     PersistentSymbolTableItem item;
0401     item.id = id;
0402 
0403     // This function does not modify the item repository. write() rather than read() is called only in order to modify
0404     // declarationsCache and importsCache. Making these two cache data members mutable would allow to call read() here.
0405     LockedItemRepository::write<PersistentSymbolTable>([&](PersistentSymbolTableRepo& repo) {
0406         ifVerifyVisitNesting(const auto guard = IterationCounter(repo);)
0407 
0408         uint index = repo.findIndex(item);
0409         if (!index) {
0410             return;
0411         }
0412 
0413         const PersistentSymbolTableItem* repositoryItem = repo.itemFromIndex(index);
0414         const auto declarations = Declarations(repositoryItem->declarations(), repositoryItem->declarationsSize(),
0415                                                repositoryItem->centralFreeItem);
0416 
0417         // TODO Qt6: revisit when porting as this code - when called recursively - relies on QHash reference stability:
0418         // 1. `cachedImports` is a copy of a value inside importsCache, so it doesn't rely on QHash reference stability.
0419         // 2. `cached` is a reference to a value inside declarationsCache, but it is not used after the visitor is
0420         //    called, so it does not rely on QHash reference stability either.
0421         // 3. `cache` is a reference to a value inside a value inside declarationsCache. `cache` is filled before
0422         //    `filterIterator` is constructed, so it is recursion-safe. `cache` is not used after the visitor is called,
0423         //    so it also does not rely on QHash reference stability.
0424         // 4. `filterIterator` contains a copy of the constData() pointer of a KDevVarLengthArray value inside a QHash.
0425         //    If QVarLengthArray::size() is less or equal to its Prealloc, constData() points to an array within the
0426         //    QVarLengthArray object. A next recursive call to visitFilteredDeclarations() may insert an id into
0427         //    declarationsCache or a visibility into a value of declarationsCache. Such an insertion can move the
0428         //    QVarLengthArray object in memory and make the copy of constData() pointer in `filterIterator` dangling.
0429         //    A possible fix is to store raw pointers KDevVarLengthArray* as values of values of declarationsCache;
0430         //    delete the pointers in ~PersistentSymbolTableRepo() and before removing values or values of values of
0431         //    declarationsCache. An alternative fix is to create a local copy of the KDevVarLengthArray and pass the
0432         //    copy's constData() to filterIterator. Better yet, make CachedDeclarations = QVector<IndexedDeclaration>
0433         //    instead of KDevVarLengthArray<IndexedDeclaration>: the QVector::constData() pointer does not change until
0434         //    the QVector is modified, which it isn't in this case.
0435         CachedIndexedRecursiveImports cachedImports;
0436         auto it = repo.importsCache.constFind(visibility);
0437         if (it != repo.importsCache.constEnd()) {
0438             cachedImports = *it;
0439         } else {
0440             cachedImports = CachedIndexedRecursiveImports(visibility.set().stdSet());
0441             repo.importsCache.insert(visibility, cachedImports);
0442         }
0443 
0444         auto filterIterator = [&]() {
0445             if (declarations.dataSize() > MinimumCountForCache) {
0446                 // Do visibility caching
0447                 auto& cached = repo.declarationsCache[id];
0448                 auto cacheIt = cached.constFind(visibility);
0449                 if (cacheIt != cached.constEnd()) {
0450                     return FilteredDeclarationIterator(
0451                         Declarations::Iterator(cacheIt->constData(), cacheIt->size(), -1), cachedImports);
0452                 }
0453 
0454                 auto insertIt = cached.insert(visibility, {});
0455                 auto& cache = *insertIt;
0456                 {
0457                     auto cacheVisitor = [&cache](const IndexedDeclaration& decl) {
0458                         cache.append(decl);
0459                     };
0460 
0461                     using FilteredDeclarationCacheVisitor =
0462                         ConvenientEmbeddedSetTreeFilterVisitor<IndexedDeclaration, IndexedDeclarationHandler,
0463                                                                IndexedTopDUContext, CachedIndexedRecursiveImports,
0464                                                                DeclarationTopContextExtractor, decltype(cacheVisitor)>;
0465 
0466                     // The visitor visits all the declarations from within its constructor
0467                     FilteredDeclarationCacheVisitor visitor(cacheVisitor, declarations.iterator(), cachedImports);
0468                 }
0469 
0470                 return FilteredDeclarationIterator(Declarations::Iterator(cache.constData(), cache.size(), -1),
0471                                                    cachedImports, true);
0472             } else {
0473                 return FilteredDeclarationIterator(declarations.iterator(), cachedImports);
0474             }
0475         }();
0476 
0477         for (; filterIterator; ++filterIterator) {
0478             if (visitor(*filterIterator) == VisitorState::Break) {
0479                 break;
0480             }
0481         }
0482     });
0483 }
0484 
0485 struct DebugVisitor
0486 {
0487     explicit DebugVisitor(const QTextStream& _out)
0488         : out(_out)
0489     {
0490     }
0491 
0492     bool operator()(const PersistentSymbolTableItem* item)
0493     {
0494         QDebug qout = fromTextStream(out);
0495         QualifiedIdentifier id(item->id.identifier());
0496         if (identifiers.contains(id)) {
0497             qout << "identifier" << id.toString() << "appears for" << identifiers[id] << "th time";
0498         }
0499 
0500         ++identifiers[id];
0501 
0502         for (uint a = 0; a < item->declarationsSize(); ++a) {
0503             IndexedDeclaration decl(item->declarations()[a]);
0504             if (!decl.isDummy()) {
0505                 if (declarations.contains(decl)) {
0506                     qout << "declaration found for multiple identifiers. Previous identifier:"
0507                          << declarations[decl].toString() << "current identifier:" << id.toString() << Qt::endl;
0508                 } else {
0509                     declarations.insert(decl, id);
0510                 }
0511             }
0512             if (decl.data() && decl.data()->qualifiedIdentifier() != item->id.identifier()) {
0513                 qout << decl.data()->url().str() << "declaration" << decl.data()->qualifiedIdentifier()
0514                      << "is registered as" << item->id.identifier() << Qt::endl;
0515             }
0516 
0517             const QString url = IndexedTopDUContext(decl.topContextIndex()).url().str();
0518             if (!decl.data() && !decl.isDummy()) {
0519                 qout << "Item in symbol-table is invalid:" << id.toString() << "- localIndex:" << decl.localIndex()
0520                      << "- url:" << url << Qt::endl;
0521             } else {
0522                 qout << "Item in symbol-table:" << id.toString() << "- localIndex:" << decl.localIndex() << "- url:" <<
0523                     url;
0524                 if (auto d = decl.data()) {
0525                     qout << "- range:" << d->range();
0526                 } else {
0527                     qout << "- null declaration";
0528                 }
0529                 qout << Qt::endl;
0530             }
0531         }
0532 
0533         return true;
0534     }
0535 
0536     const QTextStream& out;
0537     QHash<QualifiedIdentifier, uint> identifiers;
0538     QHash<IndexedDeclaration, QualifiedIdentifier> declarations;
0539 };
0540 
0541 void PersistentSymbolTable::dump(const QTextStream& out)
0542 {
0543     QDebug qout = fromTextStream(out);
0544     DebugVisitor v(out);
0545 
0546     LockedItemRepository::read<PersistentSymbolTable>([&](const PersistentSymbolTableRepo& repo) {
0547         Q_ASSERT_X(repo.ongoingIterations == 0, Q_FUNC_INFO, "don't call dump directly from a visitor");
0548 
0549         repo.visitAllItems(v);
0550 
0551         qout << "Statistics:" << Qt::endl;
0552         qout << repo.statistics() << Qt::endl;
0553     });
0554 }
0555 
0556 PersistentSymbolTable& PersistentSymbolTable::self()
0557 {
0558     static PersistentSymbolTable ret;
0559     return ret;
0560 }
0561 }