kde-extraapps/kdevplatform/language/duchain/topducontext.cpp
2015-07-26 14:23:17 +03:00

1218 lines
42 KiB
C++

/* This is part of KDevelop
Copyright 2006 Hamish Rodda <rodda@kde.org>
Copyright 2007-2009 David Nolden <david.nolden.kdevelop@art-master.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "topducontext.h"
#include "topducontextutils.h"
#include <limits>
#include <QtCore/QThread>
#include <util/kdevvarlengtharray.h>
#include "persistentsymboltable.h"
#include "problem.h"
#include "declaration.h"
#include "duchain.h"
#include "duchainlock.h"
#include "parsingenvironment.h"
#include "duchainpointer.h"
#include "declarationid.h"
#include "namespacealiasdeclaration.h"
#include "aliasdeclaration.h"
#include "abstractfunctiondeclaration.h"
#include "uses.h"
#include "topducontextdata.h"
#include "duchainregister.h"
#include "topducontextdynamicdata.h"
// #define DEBUG_SEARCH
const uint maxApplyAliasesRecursion = 100;
namespace KDevelop
{
Utils::BasicSetRepository* RecursiveImportRepository::repository() {
static Utils::BasicSetRepository recursiveImportRepositoryObject("Recursive Imports", &KDevelop::globalItemRepositoryRegistry());
return &recursiveImportRepositoryObject;
}
ReferencedTopDUContext::ReferencedTopDUContext(TopDUContext* context) : m_topContext(context) {
if(m_topContext)
DUChain::self()->refCountUp(m_topContext);
}
ReferencedTopDUContext::ReferencedTopDUContext(const ReferencedTopDUContext& rhs) : m_topContext(rhs.m_topContext) {
if(m_topContext)
DUChain::self()->refCountUp(m_topContext);
}
ReferencedTopDUContext::~ReferencedTopDUContext() {
if(m_topContext && !DUChain::deleted())
DUChain::self()->refCountDown(m_topContext);
}
ReferencedTopDUContext& ReferencedTopDUContext::operator=(const ReferencedTopDUContext& rhs) {
if(m_topContext == rhs.m_topContext)
return *this;
if(m_topContext)
DUChain::self()->refCountDown(m_topContext);
m_topContext = rhs.m_topContext;
if(m_topContext)
DUChain::self()->refCountUp(m_topContext);
return *this;
}
DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_usedDeclarationIds, DeclarationId)
DEFINE_LIST_MEMBER_HASH(TopDUContextData, m_problems, LocalIndexedProblem)
REGISTER_DUCHAIN_ITEM(TopDUContext);
template <class T>
void removeFromVector(QVector<T>& vec, const T& t) {
for(int a =0; a < vec.size(); ++a) {
if(vec[a] == t) {
vec.remove(a);
removeFromVector(vec, t);
return;
}
}
}
QMutex importStructureMutex(QMutex::Recursive);
//Contains data that is not shared among top-contexts that share their duchain entries
class TopDUContextLocalPrivate {
public:
TopDUContextLocalPrivate (TopDUContext* ctxt, uint index) :
m_ctxt(ctxt), m_ownIndex(index), m_inDuChain(false)
{
m_indexedRecursiveImports.insert(index);
}
~TopDUContextLocalPrivate() {
//Either we use some other contexts data and have no users, or we own the data and have users that share it.
QMutexLocker lock(&importStructureMutex);
foreach(const DUContext::Import& import, m_importedContexts)
if(DUChain::self()->isInMemory(import.topContextIndex()) && dynamic_cast<TopDUContext*>(import.context(0)))
dynamic_cast<TopDUContext*>(import.context(0))->m_local->m_directImporters.remove(m_ctxt);
}
///@todo Make all this work consistently together with import-caching
//After loading, should rebuild the links
void rebuildDynamicImportStructure() {
//Currently we do not store the whole data in TopDUContextLocalPrivate, so we reconstruct it from what was stored by DUContext.
Q_ASSERT(m_importedContexts.isEmpty());
FOREACH_FUNCTION(const DUContext::Import& import, m_ctxt->d_func()->m_importedContexts) {
if(DUChain::self()->isInMemory(import.topContextIndex())) {
Q_ASSERT(import.context(0));
TopDUContext* top = import.context(0)->topContext();
Q_ASSERT(top);
addImportedContextRecursively(top, false, true);
}
}
FOREACH_FUNCTION(const IndexedDUContext& importer, m_ctxt->d_func()->m_importers) {
if(DUChain::self()->isInMemory(importer.topContextIndex())) {
Q_ASSERT(importer.context());
TopDUContext* top = importer.context()->topContext();
Q_ASSERT(top);
top->m_local->addImportedContextRecursively(m_ctxt, false, true);
}
}
}
//Index of this top-context within the duchain
//Since the data of top-contexts can be shared among multiple, this can be used to add imports that are local to this top-context.
QVector<DUContext::Import> m_importedContexts;
// mutable bool m_haveImportStructure : 1;
TopDUContext* m_ctxt;
QSet<DUContext*> m_directImporters;
ParsingEnvironmentFilePointer m_file;
KSharedPtr<IAstContainer> m_ast;
uint m_ownIndex;
bool m_inDuChain;
void clearImportedContextsRecursively() {
QMutexLocker lock(&importStructureMutex);
// Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1);
QSet<QPair<TopDUContext*, const TopDUContext*> > rebuild;
foreach(const DUContext::Import &import, m_importedContexts) {
TopDUContext* top = dynamic_cast<TopDUContext*>(import.context(0));
if(top) {
top->m_local->m_directImporters.remove(m_ctxt);
if(!m_ctxt->usingImportsCache()) {
removeImportedContextRecursion(top, top, 1, rebuild);
QHash<const TopDUContext*, QPair<int, const TopDUContext*> > b = top->m_local->m_recursiveImports;
for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) {
if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == top)
removeImportedContextRecursion(top, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context
}
}
}
}
m_importedContexts.clear();
rebuildImportStructureRecursion(rebuild);
Q_ASSERT(m_recursiveImports.isEmpty());
// Q_ASSERT(m_recursiveImports.size() == m_indexedRecursiveImports.count()-1);
}
//Adds the context to this and all contexts that import this, and manages m_recursiveImports
void addImportedContextRecursively(TopDUContext* context, bool temporary, bool local) {
QMutexLocker lock(&importStructureMutex);
context->m_local->m_directImporters.insert(m_ctxt);
if(local)
m_importedContexts << DUContext::Import(context, m_ctxt);
if(!m_ctxt->usingImportsCache()) {
addImportedContextRecursion(context, context, 1, temporary);
QHash<const TopDUContext*, QPair<int, const TopDUContext*> > b = context->m_local->m_recursiveImports;
for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it)
addImportedContextRecursion(context, it.key(), (*it).first+1, temporary); //Add contexts that were imported earlier into the given one
}
}
//Removes the context from this and all contexts that import this, and manages m_recursiveImports
void removeImportedContextRecursively(TopDUContext* context, bool local) {
QMutexLocker lock(&importStructureMutex);
context->m_local->m_directImporters.remove(m_ctxt);
if(local)
removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt));
QSet<QPair<TopDUContext*, const TopDUContext*> > rebuild;
if(!m_ctxt->usingImportsCache()) {
removeImportedContextRecursion(context, context, 1, rebuild);
QHash<const TopDUContext*, QPair<int, const TopDUContext*> > b = context->m_local->m_recursiveImports;
for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) {
if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context)
removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context
}
}
rebuildImportStructureRecursion(rebuild);
}
void removeImportedContextsRecursively(const QList<TopDUContext*>& contexts, bool local) {
QMutexLocker lock(&importStructureMutex);
QSet<QPair<TopDUContext*, const TopDUContext*> > rebuild;
foreach(TopDUContext* context, contexts) {
context->m_local->m_directImporters.remove(m_ctxt);
if(local)
removeFromVector(m_importedContexts, DUContext::Import(context, m_ctxt));
if(!m_ctxt->usingImportsCache()) {
removeImportedContextRecursion(context, context, 1, rebuild);
QHash<const TopDUContext*, QPair<int, const TopDUContext*> > b = context->m_local->m_recursiveImports;
for(RecursiveImports::const_iterator it = b.constBegin(); it != b.constEnd(); ++it) {
if(m_recursiveImports.contains(it.key()) && m_recursiveImports[it.key()].second == context)
removeImportedContextRecursion(context, it.key(), it->first+1, rebuild); //Remove all contexts that are imported through the context
}
}
}
rebuildImportStructureRecursion(rebuild);
}
//Has an entry for every single recursively imported file, that contains the shortest path, and the next context on that path to the imported context.
//This does not need to be stored to disk, because it is defined implicitly.
//What makes this most complicated is the fact that loops are allowed in the import structure.
typedef QHash<const TopDUContext*, QPair<int, const TopDUContext*> > RecursiveImports;
mutable RecursiveImports m_recursiveImports;
mutable TopDUContext::IndexedRecursiveImports m_indexedRecursiveImports;
private:
void addImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int depth, bool temporary = false) {
if(m_ctxt->usingImportsCache())
return;
// if(!m_haveImportStructure)
// return;
if(imported == m_ctxt)
return;
const bool computeShortestPaths = false; ///@todo We do not compute the shortest path. Think what's right.
// traceNext->m_local->needImportStructure();
// imported->m_local->needImportStructure();
RecursiveImports::iterator it = m_recursiveImports.find(imported);
if(it == m_recursiveImports.end()) {
//Insert new path to "imported"
m_recursiveImports[imported] = qMakePair(depth, traceNext);
m_indexedRecursiveImports.insert(imported->indexed());
// Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size()+1);
Q_ASSERT(traceNext != m_ctxt);
}else{
if(!computeShortestPaths)
return;
if(temporary) //For temporary imports, we don't record the best path.
return;
//It would be better if we would use the following code, but it creates too much cost in updateImportedContextRecursion when imports are removed again.
//Check whether the new way to "imported" is shorter than the stored one
if((*it).first > depth) {
//Add a shorter path
(*it).first = depth;
Q_ASSERT(traceNext);
(*it).second = traceNext;
Q_ASSERT(traceNext == imported || (traceNext->m_local->m_recursiveImports.contains(imported) && traceNext->m_local->m_recursiveImports[imported].first < (*it).first));
}else{
//The imported context is already imported through a same/better path, so we can just stop processing. This saves us from endless recursion.
return;
}
}
if(temporary)
return;
for(QSet<DUContext*>::const_iterator it = m_directImporters.constBegin(); it != m_directImporters.constEnd(); ++it) {
TopDUContext* top = dynamic_cast<TopDUContext*>(const_cast<DUContext*>(*it)); //Avoid detaching, so use const_cast
if(top) ///@todo also record this for local imports
top->m_local->addImportedContextRecursion(m_ctxt, imported, depth+1);
}
}
void removeImportedContextRecursion(const TopDUContext* traceNext, const TopDUContext* imported, int distance, QSet<QPair<TopDUContext*, const TopDUContext*> >& rebuild) {
if(m_ctxt->usingImportsCache())
return;
if(imported == m_ctxt)
return;
// if(!m_haveImportStructure)
// return;
RecursiveImports::iterator it = m_recursiveImports.find(imported);
if(it == m_recursiveImports.end()) {
//We don't import. Just return, this saves us from endless recursion.
return;
}else{
//Check whether we have imported "imported" through "traceNext". If not, return. Else find a new trace.
if((*it).second == traceNext && (*it).first == distance) {
//We need to remove the import through traceNext. Check whether there is another imported context that imports it.
m_recursiveImports.erase(it); //In order to prevent problems, we completely remove everything, and re-add it.
//Just updating these complex structures is very hard.
Q_ASSERT(imported != m_ctxt);
m_indexedRecursiveImports.remove(imported->indexed());
// Q_ASSERT(m_indexedRecursiveImports.size() == m_recursiveImports.size());
rebuild.insert(qMakePair(m_ctxt, imported));
//We MUST do this before finding another trace, because else we would create loops
for(QSet<DUContext*>::const_iterator childIt = m_directImporters.constBegin(); childIt != m_directImporters.constEnd(); ++childIt) {
TopDUContext* top = dynamic_cast<TopDUContext*>(const_cast<DUContext*>(*childIt)); //Avoid detaching, so use const iterator
if(top)
top->m_local->removeImportedContextRecursion(m_ctxt, imported, distance+1, rebuild); //Don't use 'it' from here on, it may be invalid
}
}
}
}
//Updates the trace to 'imported'
void rebuildStructure(const TopDUContext* imported);
void rebuildImportStructureRecursion(const QSet<QPair<TopDUContext*, const TopDUContext*> >& rebuild) {
for(QSet<QPair<TopDUContext*, const TopDUContext*> >::const_iterator it = rebuild.constBegin(); it != rebuild.constEnd(); ++it) {
//for(int a = rebuild.size()-1; a >= 0; --a) {
//Find the best imported parent
it->first->m_local->rebuildStructure(it->second);
}
}
};
///Takes a set of conditions in the constructors, and checks with each call to operator() whether these conditions are fulfilled on the given declaration.
///The import-structure needs to be constructed and locked when this is used
TopDUContext::DeclarationChecker::DeclarationChecker(const TopDUContext* _top, const CursorInRevision& _position, const AbstractType::Ptr& _dataType, DUContext::SearchFlags _flags, KDevVarLengthArray<IndexedDeclaration>* _createVisibleCache)
: createVisibleCache(_createVisibleCache)
, top(_top)
, topDFunc(_top->d_func())
, position(_position)
, dataType(_dataType)
, flags(_flags)
{
}
bool TopDUContext::DeclarationChecker::operator()(const Declaration* decl) const
{
if(!decl)
return false;
if (top != decl->topContext()) {
if((flags & DUContext::OnlyFunctions) && !dynamic_cast<const AbstractFunctionDeclaration*>(decl))
return false;
if (dataType && decl->abstractType()->indexed() != dataType->indexed())
// The declaration doesn't match the type filter we are applying
return false;
} else {
if((flags & DUContext::OnlyFunctions) && !dynamic_cast<const AbstractFunctionDeclaration*>(decl))
return false;
if (dataType && decl->abstractType() != dataType)
// The declaration doesn't match the type filter we are applying
return false;
if (decl->range().start >= position)
if(!decl->context() || decl->context()->type() != DUContext::Class)
return false; // The declaration is behind the position we're searching from, therefore not accessible
}
// Success, this declaration is accessible
return true;
}
const TopDUContext::IndexedRecursiveImports& TopDUContext::recursiveImportIndices() const
{
// No lock-check for performance reasons
QMutexLocker lock(&importStructureMutex);
if(!d_func()->m_importsCache.isEmpty())
return d_func()->m_importsCache;
return m_local->m_indexedRecursiveImports;
}
void TopDUContextData::updateImportCacheRecursion(uint baseIndex, IndexedTopDUContext currentContext, TopDUContext::IndexedRecursiveImports& visited) {
if(visited.contains(currentContext.index()))
return;
Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called
if(!currentContext.data()) {
kDebug() << "importing invalid context";
return;
}
visited.insert(currentContext.index());
const TopDUContextData* currentData = currentContext.data()->topContext()->d_func();
if(currentData->m_importsCache.contains(baseIndex) || currentData->m_importsCache.isEmpty())
{
//If we have a loop or no imports-cache is used, we have to look at each import separately.
const KDevelop::DUContext::Import* imports = currentData->m_importedContexts();
uint importsSize = currentData->m_importedContextsSize();
for(uint a = 0; a < importsSize; ++a) {
IndexedTopDUContext next(imports[a].topContextIndex());
if(next.isValid())
updateImportCacheRecursion(baseIndex, next, visited);
}
}else{
//If we don't have a loop with baseIndex, we can safely just merge with the imported importscache
visited += currentData->m_importsCache;
}
}
void TopDUContextData::updateImportCacheRecursion(IndexedTopDUContext currentContext, std::set<uint>& visited) {
if(visited.find(currentContext.index()) != visited.end())
return;
Q_ASSERT(currentContext.index()); //The top-context must be in the repository when this is called
if(!currentContext.data()) {
kDebug() << "importing invalid context";
return;
}
visited.insert(currentContext.index());
const TopDUContextData* currentData = currentContext.data()->topContext()->d_func();
const KDevelop::DUContext::Import* imports = currentData->m_importedContexts();
uint importsSize = currentData->m_importedContextsSize();
for(uint a = 0; a < importsSize; ++a) {
IndexedTopDUContext next(imports[a].topContextIndex());
if(next.isValid())
updateImportCacheRecursion(next, visited);
}
}
void TopDUContext::updateImportsCache() {
QMutexLocker lock(&importStructureMutex);
const bool use_fully_recursive_import_cache_computation = false;
if(use_fully_recursive_import_cache_computation) {
std::set<uint> visited;
TopDUContextData::updateImportCacheRecursion(this, visited);
Q_ASSERT(visited.find(ownIndex()) != visited.end());
d_func_dynamic()->m_importsCache = IndexedRecursiveImports(visited);
}else{
d_func_dynamic()->m_importsCache = IndexedRecursiveImports();
TopDUContextData::updateImportCacheRecursion(ownIndex(), this, d_func_dynamic()->m_importsCache);
}
Q_ASSERT(d_func_dynamic()->m_importsCache.contains(IndexedTopDUContext(this)));
Q_ASSERT(usingImportsCache());
Q_ASSERT(imports(this, CursorInRevision::invalid()));
if(parsingEnvironmentFile())
parsingEnvironmentFile()->setImportsCache(d_func()->m_importsCache);
}
bool TopDUContext::usingImportsCache() const {
return !d_func()->m_importsCache.isEmpty();
}
CursorInRevision TopDUContext::importPosition(const DUContext* target) const
{
ENSURE_CAN_READ
DUCHAIN_D(DUContext);
Import import(const_cast<DUContext*>(target), const_cast<TopDUContext*>(this), CursorInRevision::invalid());
for(unsigned int a = 0; a < d->m_importedContextsSize(); ++a)
if(d->m_importedContexts()[a] == import)
return d->m_importedContexts()[a].position;
return DUContext::importPosition(target);
}
void TopDUContextLocalPrivate::rebuildStructure(const TopDUContext* imported) {
if(m_ctxt == imported)
return;
for(QVector<DUContext::Import>::const_iterator parentIt = m_importedContexts.constBegin(); parentIt != m_importedContexts.constEnd(); ++parentIt) {
TopDUContext* top = dynamic_cast<TopDUContext*>(const_cast<DUContext*>(parentIt->context(0))); //To avoid detaching, use const iterator
if(top) {
// top->m_local->needImportStructure();
if(top == imported) {
addImportedContextRecursion(top, imported, 1);
}else{
RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported);
if(it2 != top->m_local->m_recursiveImports.constEnd()) {
addImportedContextRecursion(top, imported, (*it2).first + 1);
}
}
}
}
for(unsigned int a = 0; a < m_ctxt->d_func()->m_importedContextsSize(); ++a) {
TopDUContext* top = dynamic_cast<TopDUContext*>(const_cast<DUContext*>(m_ctxt->d_func()->m_importedContexts()[a].context(0))); //To avoid detaching, use const iterator
if(top) {
// top->m_local->needImportStructure();
if(top == imported) {
addImportedContextRecursion(top, imported, 1);
}else{
RecursiveImports::const_iterator it2 = top->m_local->m_recursiveImports.constFind(imported);
if(it2 != top->m_local->m_recursiveImports.constEnd()) {
addImportedContextRecursion(top, imported, (*it2).first + 1);
}
}
}
}
}
void TopDUContext::rebuildDynamicImportStructure() {
m_local->rebuildDynamicImportStructure();
}
void TopDUContext::rebuildDynamicData(DUContext* parent, uint ownIndex) {
Q_ASSERT(parent == 0 && ownIndex != 0);
m_local->m_ownIndex = ownIndex;
DUContext::rebuildDynamicData(parent, 0);
}
IndexedTopDUContext TopDUContext::indexed() const {
return IndexedTopDUContext(m_local->m_ownIndex);
}
uint TopDUContext::ownIndex() const
{
return m_local->m_ownIndex;
}
TopDUContext::TopDUContext(TopDUContextData& data) : DUContext(data), m_local(new TopDUContextLocalPrivate(this, data.m_ownIndex)), m_dynamicData(new TopDUContextDynamicData(this)) {
}
TopDUContext::TopDUContext(const IndexedString& url, const RangeInRevision& range, ParsingEnvironmentFile* file)
: DUContext(*new TopDUContextData(url), range)
, m_local(new TopDUContextLocalPrivate(this, DUChain::newTopContextIndex()))
, m_dynamicData(new TopDUContextDynamicData(this))
{
d_func_dynamic()->setClassId(this);
setType(Global);
DUCHAIN_D_DYNAMIC(TopDUContext);
d->m_features = VisibleDeclarationsAndContexts;
d->m_ownIndex = m_local->m_ownIndex;
setParsingEnvironmentFile(file);
setInSymbolTable(true);
}
KSharedPtr<ParsingEnvironmentFile> TopDUContext::parsingEnvironmentFile() const {
return m_local->m_file;
}
TopDUContext::~TopDUContext( )
{
m_dynamicData->m_deleting = true;
//Clear the AST, so that the 'feature satisfaction' cache is eventually updated
clearAst();
if(!isOnDisk())
{
//Clear the 'feature satisfaction' cache which is managed in ParsingEnvironmentFile
setFeatures(Empty);
clearUsedDeclarationIndices();
}
deleteChildContextsRecursively();
deleteLocalDeclarations();
m_dynamicData->clear();
}
void TopDUContext::deleteSelf() {
//We've got to make sure that m_dynamicData and m_local are still valid while all the sub-contexts are destroyed
TopDUContextLocalPrivate* local = m_local;
TopDUContextDynamicData* dynamicData = m_dynamicData;
m_dynamicData->m_deleting = true;
delete this;
delete local;
delete dynamicData;
}
TopDUContext::Features TopDUContext::features() const
{
uint ret = d_func()->m_features;
if(ast())
ret |= TopDUContext::AST;
return (TopDUContext::Features)ret;
}
void TopDUContext::setFeatures(Features features)
{
features = (TopDUContext::Features)(features & (~Recursive)); //Remove the "Recursive" flag since that's only for searching
features = (TopDUContext::Features)(features & (~ForceUpdateRecursive)); //Remove the update flags
features = (TopDUContext::Features)(features & (~AST)); //Remove the AST flag, it's only used while updating
d_func_dynamic()->m_features = features;
//Replicate features to ParsingEnvironmentFile
if(parsingEnvironmentFile())
parsingEnvironmentFile()->setFeatures(this->features());
}
void TopDUContext::setAst(KSharedPtr<IAstContainer> ast)
{
ENSURE_CAN_WRITE
m_local->m_ast = ast;
if(parsingEnvironmentFile())
parsingEnvironmentFile()->setFeatures(features());
}
void TopDUContext::setParsingEnvironmentFile(ParsingEnvironmentFile* file)
{
if(m_local->m_file) //Clear the "feature satisfaction" cache
m_local->m_file->setFeatures(Empty);
//We do not enforce a duchain lock here, since this is also used while loading a top-context
m_local->m_file = KSharedPtr<ParsingEnvironmentFile>(file);
//Replicate features to ParsingEnvironmentFile
if(file) {
file->setTopContext(IndexedTopDUContext(ownIndex()));
Q_ASSERT(file->indexedTopContext().isValid());
file->setFeatures(d_func()->m_features);
file->setImportsCache(d_func()->m_importsCache);
}
}
struct TopDUContext::FindDeclarationsAcceptor {
FindDeclarationsAcceptor(const TopDUContext* _top, DeclarationList& _target, const DeclarationChecker& _check, SearchFlags _flags) : top(_top), target(_target), check(_check) {
flags = _flags;
}
bool operator() (const QualifiedIdentifier& id) {
#ifdef DEBUG_SEARCH
kDebug() << "accepting" << id.toString();
#endif
PersistentSymbolTable::Declarations allDecls;
//This iterator efficiently filters the visible declarations out of all declarations
PersistentSymbolTable::FilteredDeclarationIterator filter;
//This is used if filtering is disabled
PersistentSymbolTable::Declarations::Iterator unchecked;
if(check.flags & DUContext::NoImportsCheck) {
allDecls = PersistentSymbolTable::self().getDeclarations(id);
unchecked = allDecls.iterator();
} else
filter = PersistentSymbolTable::self().getFilteredDeclarations(id, top->recursiveImportIndices());
while(filter || unchecked) {
IndexedDeclaration iDecl;
if(filter) {
iDecl = *filter;
++filter;
} else {
iDecl = *unchecked;
++unchecked;
}
Declaration* decl = iDecl.data();
if(!decl)
continue;
if(!check(decl))
continue;
if( ! (flags & DontResolveAliases) && decl->kind() == Declaration::Alias ) {
//Apply alias declarations
AliasDeclaration* alias = static_cast<AliasDeclaration*>(decl);
if(alias->aliasedDeclaration().isValid()) {
decl = alias->aliasedDeclaration().declaration();
} else {
kDebug() << "lost aliased declaration";
}
}
target.append(decl);
}
check.createVisibleCache = 0;
return !top->foundEnough(target, flags);
}
const TopDUContext* top;
DeclarationList& target;
const DeclarationChecker& check;
QFlags< KDevelop::DUContext::SearchFlag > flags;
};
bool TopDUContext::findDeclarationsInternal(const SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DeclarationList& ret, const TopDUContext* /*source*/, SearchFlags flags, uint /*depth*/) const
{
ENSURE_CAN_READ
#ifdef DEBUG_SEARCH
FOREACH_ARRAY(const SearchItem::Ptr& idTree, identifiers)
foreach(const QualifiedIdentifier &id, idTree->toList())
kDebug() << "searching item" << id.toString();
#endif
DeclarationChecker check(this, position, dataType, flags);
FindDeclarationsAcceptor storer(this, ret, check, flags);
///The actual scopes are found within applyAliases, and each complete qualified identifier is given to FindDeclarationsAcceptor.
///That stores the found declaration to the output.
applyAliases(identifiers, storer, position, false);
return true;
}
//This is used to prevent endless recursion due to "using namespace .." declarations, by storing all imports that are already being used.
struct TopDUContext::ApplyAliasesBuddyInfo {
ApplyAliasesBuddyInfo(uint importChainType, ApplyAliasesBuddyInfo* predecessor, IndexedQualifiedIdentifier importId) : m_importChainType(importChainType), m_predecessor(predecessor), m_importId(importId) {
if(m_predecessor && m_predecessor->m_importChainType != importChainType)
m_predecessor = 0;
}
//May also be called when this is zero.
bool alreadyImporting(IndexedQualifiedIdentifier id) {
ApplyAliasesBuddyInfo* current = this;
while(current) {
if(current->m_importId == id)
return true;
current = current->m_predecessor;
}
return false;
}
uint m_importChainType;
ApplyAliasesBuddyInfo* m_predecessor;
IndexedQualifiedIdentifier m_importId;
};
///@todo Implement a cache so at least the global import checks don't need to be done repeatedly. The cache should be thread-local, using DUChainPointer for the hashed items, and when an item was deleted, it should be discarded
template<class Acceptor>
bool TopDUContext::applyAliases( const QualifiedIdentifier& previous, const SearchItem::Ptr& identifier, Acceptor& accept, const CursorInRevision& position, bool canBeNamespace, ApplyAliasesBuddyInfo* buddy, uint recursionDepth ) const
{
if(recursionDepth > maxApplyAliasesRecursion) {
QList<QualifiedIdentifier> searches = identifier->toList();
QualifiedIdentifier id;
if(!searches.isEmpty())
id = searches.first();
kDebug() << "maximum apply-aliases recursion reached while searching" << id;
}
bool foundAlias = false;
QualifiedIdentifier id(previous);
id.push(identifier->identifier);
if(!id.inRepository())
return true; //If the qualified identifier is not in the identifier repository, it cannot be registered anywhere, so there's nothing we need to do
if( !identifier->next.isEmpty() || canBeNamespace ) { //If it cannot be a namespace, the last part of the scope will be ignored
//Search for namespace-aliases, by using globalAliasIdentifier, which is inserted into the symbol-table by NamespaceAliasDeclaration
QualifiedIdentifier aliasId(id);
aliasId.push(globalIndexedAliasIdentifier());
#ifdef DEBUG_SEARCH
kDebug() << "checking" << id.toString();
#endif
if(aliasId.inRepository()) {
//This iterator efficiently filters the visible declarations out of all declarations
PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(aliasId, recursiveImportIndices());
if(filter) {
DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0);
//The first part of the identifier has been found as a namespace-alias.
//In c++, we only need the first alias. However, just to be correct, follow them all for now.
for(; filter; ++filter)
{
Declaration* aliasDecl = filter->data();
if(!aliasDecl)
continue;
if(!check(aliasDecl))
continue;
if(aliasDecl->kind() != Declaration::NamespaceAlias)
continue;
if(foundAlias)
break;
Q_ASSERT(dynamic_cast<NamespaceAliasDeclaration*>(aliasDecl));
NamespaceAliasDeclaration* alias = static_cast<NamespaceAliasDeclaration*>(aliasDecl);
foundAlias = true;
QualifiedIdentifier importIdentifier = alias->importIdentifier();
if(importIdentifier.isEmpty()) {
kDebug() << "found empty import";
continue;
}
if(buddy->alreadyImporting( importIdentifier ))
continue; //This import has already been applied to this search
ApplyAliasesBuddyInfo info(1, buddy, importIdentifier);
if(identifier->next.isEmpty()) {
//Just insert the aliased namespace identifier
if(!accept(importIdentifier))
return false;
}else{
//Create an identifiers where namespace-alias part is replaced with the alias target
FOREACH_ARRAY(const SearchItem::Ptr& item, identifier->next)
if(!applyAliases(importIdentifier, item, accept, position, canBeNamespace, &info, recursionDepth+1))
return false;
}
}
}
}
}
if(!foundAlias) { //If we haven't found an alias, put the current versions into the result list. Additionally we will compute the identifiers transformed through "using".
if(identifier->next.isEmpty()) {
if(!accept(id)) //We're at the end of a qualified identifier, accept it
return false;
} else {
FOREACH_ARRAY(const SearchItem::Ptr& next, identifier->next)
if(!applyAliases(id, next, accept, position, canBeNamespace, 0, recursionDepth+1))
return false;
}
}
/*if( !prefix.explicitlyGlobal() || !prefix.isEmpty() ) {*/ ///@todo check iso c++ if using-directives should be respected on top-level when explicitly global
///@todo this is bad for a very big repository(the chains should be walked for the top-context instead)
//Find all namespace-imports at given scope
{
QualifiedIdentifier importId(previous);
importId.push(globalIndexedImportIdentifier());
#ifdef DEBUG_SEARCH
// kDebug() << "checking imports in" << (backPointer ? id.toString() : QString("global"));
#endif
if(importId.inRepository()) {
//This iterator efficiently filters the visible declarations out of all declarations
PersistentSymbolTable::FilteredDeclarationIterator filter = PersistentSymbolTable::self().getFilteredDeclarations(importId, recursiveImportIndices());
if(filter) {
DeclarationChecker check(this, position, AbstractType::Ptr(), NoSearchFlags, 0);
for(; filter; ++filter)
{
Declaration* importDecl = filter->data();
if(!importDecl)
continue;
//We must never break or return from this loop, because else we might be creating a bad cache
if(!check(importDecl))
continue;
//Search for the identifier with the import-identifier prepended
Q_ASSERT(dynamic_cast<NamespaceAliasDeclaration*>(importDecl));
NamespaceAliasDeclaration* alias = static_cast<NamespaceAliasDeclaration*>(importDecl);
#ifdef DEBUG_SEARCH
kDebug() << "found import of" << alias->importIdentifier().toString();
#endif
QualifiedIdentifier importIdentifier = alias->importIdentifier();
if(importIdentifier.isEmpty()) {
kDebug() << "found empty import";
continue;
}
if(buddy->alreadyImporting( importIdentifier ))
continue; //This import has already been applied to this search
ApplyAliasesBuddyInfo info(2, buddy, importIdentifier);
if(previous != importIdentifier)
if(!applyAliases(importIdentifier, identifier, accept, importDecl->topContext() == this ? importDecl->range().start : position, canBeNamespace, &info, recursionDepth+1))
return false;
}
}
}
}
return true;
}
template<class Acceptor>
void TopDUContext::applyAliases( const SearchItem::PtrList& identifiers, Acceptor& acceptor, const CursorInRevision& position, bool canBeNamespace ) const
{
QualifiedIdentifier emptyId;
FOREACH_ARRAY(const SearchItem::Ptr& item, identifiers)
applyAliases(emptyId, item, acceptor, position, canBeNamespace, 0, 0);
}
TopDUContext * TopDUContext::topContext() const
{
return const_cast<TopDUContext*>(this);
}
bool TopDUContext::deleting() const
{
///@todo remove d_func()->m_deleting, not used any more
return m_dynamicData->m_deleting;
}
QList<ProblemPointer> TopDUContext::problems() const
{
ENSURE_CAN_READ
const auto data = d_func();
QList<ProblemPointer> ret;
ret.reserve(data->m_problemsSize());
for (uint i = 0; i < data->m_problemsSize(); ++i) {
ret << ProblemPointer(data->m_problems()[i].data(this));
}
return ret;
}
void TopDUContext::setProblems(const QList<ProblemPointer>& problems)
{
ENSURE_CAN_WRITE
clearProblems();
for (const auto& problem : problems) {
addProblem(problem);
}
}
void TopDUContext::addProblem(const ProblemPointer& problem)
{
ENSURE_CAN_WRITE
Q_ASSERT(problem);
auto data = d_func_dynamic();
// store for indexing
LocalIndexedProblem indexedProblem(problem, this);
Q_ASSERT(indexedProblem.isValid());
data->m_problemsList().append(indexedProblem);
Q_ASSERT(indexedProblem.data(this));
}
void TopDUContext::clearProblems()
{
ENSURE_CAN_WRITE
d_func_dynamic()->m_problemsList().clear();
m_dynamicData->clearProblems();
}
QVector<DUContext*> TopDUContext::importers() const
{
ENSURE_CAN_READ
return QVector<DUContext*>::fromList( m_local->m_directImporters.toList() );
}
QList<DUContext*> TopDUContext::loadedImporters() const
{
ENSURE_CAN_READ
return m_local->m_directImporters.toList();
}
QVector<DUContext::Import> TopDUContext::importedParentContexts() const
{
ENSURE_CAN_READ
return DUContext::importedParentContexts();
}
bool TopDUContext::imports(const DUContext * origin, const CursorInRevision& position) const
{
return importsPrivate(origin, position);
}
bool TopDUContext::importsPrivate(const DUContext * origin, const CursorInRevision& position) const
{
Q_UNUSED(position);
if( const TopDUContext* top = dynamic_cast<const TopDUContext*>(origin) ) {
QMutexLocker lock(&importStructureMutex);
bool ret = recursiveImportIndices().contains(IndexedTopDUContext(const_cast<TopDUContext*>(top)));
if(top == this)
Q_ASSERT(ret);
return ret;
} else {
//Cannot import a non top-context
return false;
}
}
void TopDUContext::clearImportedParentContexts() {
if(usingImportsCache()) {
d_func_dynamic()->m_importsCache = IndexedRecursiveImports();
d_func_dynamic()->m_importsCache.insert(IndexedTopDUContext(this));
}
DUContext::clearImportedParentContexts();
m_local->clearImportedContextsRecursively();
Q_ASSERT(m_local->m_recursiveImports.count() == 0);
Q_ASSERT(m_local->m_indexedRecursiveImports.count() == 1);
Q_ASSERT(imports(this, CursorInRevision::invalid()));
}
void TopDUContext::addImportedParentContext(DUContext* context, const CursorInRevision& position, bool anonymous, bool temporary) {
if(context == this)
return;
if(!dynamic_cast<TopDUContext*>(context)) {
//We cannot do this, because of the extended way we treat top-context imports.
kDebug() << "tried to import a non top-context into a top-context. This is not possible.";
return;
}
//Always make the contexts anonymous, because we care about importers in TopDUContextLocalPrivate
DUContext::addImportedParentContext(context, position, anonymous, temporary);
m_local->addImportedContextRecursively(static_cast<TopDUContext*>(context), temporary, true);
}
void TopDUContext::removeImportedParentContext(DUContext* context) {
DUContext::removeImportedParentContext(context);
m_local->removeImportedContextRecursively(static_cast<TopDUContext*>(context), true);
}
void TopDUContext::addImportedParentContexts(const QList<QPair<TopDUContext*, CursorInRevision> >& contexts, bool temporary) {
typedef QPair<TopDUContext*, CursorInRevision> Pair;
foreach(const Pair &pair, contexts)
addImportedParentContext(pair.first, pair.second, false, temporary);
}
void TopDUContext::removeImportedParentContexts(const QList<TopDUContext*>& contexts) {
foreach(TopDUContext* context, contexts)
DUContext::removeImportedParentContext(context);
m_local->removeImportedContextsRecursively(contexts, true);
}
/// Returns true if this object is registered in the du-chain. If it is not, all sub-objects(context, declarations, etc.)
bool TopDUContext::inDUChain() const {
return m_local->m_inDuChain;
}
/// This flag is only used by DUChain, never change it from outside.
void TopDUContext::setInDuChain(bool b) {
m_local->m_inDuChain = b;
}
TopDUContext::Flags TopDUContext::flags() const {
return d_func()->m_flags;
}
void TopDUContext::setFlags(Flags f) {
d_func_dynamic()->m_flags = f;
}
bool TopDUContext::isOnDisk() const {
///@todo Change this to releasingToDisk, and only enable it while saving a top-context to disk.
return m_dynamicData->isOnDisk();
}
void TopDUContext::clearUsedDeclarationIndices() {
ENSURE_CAN_WRITE
for(unsigned int a = 0; a < d_func()->m_usedDeclarationIdsSize(); ++a)
DUChain::uses()->removeUse(d_func()->m_usedDeclarationIds()[a], this);
d_func_dynamic()->m_usedDeclarationIdsList().clear();
}
void TopDUContext::deleteUsesRecursively()
{
clearUsedDeclarationIndices();
KDevelop::DUContext::deleteUsesRecursively();
}
Declaration* TopDUContext::usedDeclarationForIndex(unsigned int declarationIndex) const {
ENSURE_CAN_READ
if(declarationIndex & (1<<31)) {
//We use the highest bit to mark direct indices into the local declarations
declarationIndex &= (0xffffffff - (1<<31)); //unset the highest bit
return m_dynamicData->getDeclarationForIndex(declarationIndex);
}else if(declarationIndex < d_func()->m_usedDeclarationIdsSize())
return d_func()->m_usedDeclarationIds()[declarationIndex].getDeclaration(this);
else
return 0;
}
int TopDUContext::indexForUsedDeclaration(Declaration* declaration, bool create) {
if(create) {
ENSURE_CAN_WRITE
}else{
ENSURE_CAN_READ
}
if(!declaration) {
return std::numeric_limits<int>::max();
}
if(declaration->topContext() == this && !declaration->inSymbolTable() && !m_dynamicData->isTemporaryDeclarationIndex(declaration->ownIndex())) {
uint index = declaration->ownIndex();
Q_ASSERT(!(index & (1<<31)));
return (int)(index | (1<<31)); //We don't put context-local declarations into the list, that's a waste. We just use the mark them with the highest bit.
}
DeclarationId id(declaration->id());
int index = -1;
uint size = d_func()->m_usedDeclarationIdsSize();
const DeclarationId* ids = d_func()->m_usedDeclarationIds();
///@todo Make m_usedDeclarationIds sorted, and find the decl. using binary search
for(unsigned int a = 0; a < size; ++a)
if(ids[a] == id) {
index = a;
break;
}
if(index != -1)
return index;
if(!create)
return std::numeric_limits<int>::max();
d_func_dynamic()->m_usedDeclarationIdsList().append(id);
if(declaration->topContext() != this)
DUChain::uses()->addUse(id, this);
return d_func()->m_usedDeclarationIdsSize()-1;
}
QList<RangeInRevision> allUses(TopDUContext* context, Declaration* declaration, bool noEmptyRanges) {
QList<RangeInRevision> ret;
int declarationIndex = context->indexForUsedDeclaration(declaration, false);
if(declarationIndex == std::numeric_limits<int>::max())
return ret;
return allUses(context, declarationIndex, noEmptyRanges);
}
KSharedPtr<IAstContainer> TopDUContext::ast() const
{
return m_local->m_ast;
}
void TopDUContext::clearAst()
{
setAst(KSharedPtr<IAstContainer>(0));
}
IndexedString TopDUContext::url() const {
return d_func()->m_url;
}
}
// kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on