diff --git a/language/duchain/codemodel.cpp b/language/duchain/codemodel.cpp index 8447987a3..3e1e7551e 100644 --- a/language/duchain/codemodel.cpp +++ b/language/duchain/codemodel.cpp @@ -1,388 +1,361 @@ /* This file is part of KDevelop Copyright 2008 David Nolden 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 "codemodel.h" #include #include #include "appendedlist.h" #include "util/debug.h" #include #include "identifier.h" #include #include #include #define ifDebug(x) namespace KDevelop { class CodeModelItemHandler { public: static int leftChild(const CodeModelItem& m_data) { return (int)m_data.referenceCount; } static void setLeftChild(CodeModelItem& m_data, int child) { m_data.referenceCount = (uint)child; } static int rightChild(const CodeModelItem& m_data) { return (int)m_data.uKind; } static void setRightChild(CodeModelItem& m_data, int child) { m_data.uKind = (uint)child; } //Copies this item into the given one static void copyTo(const CodeModelItem& m_data, CodeModelItem& data) { data = m_data; } static void createFreeItem(CodeModelItem& data) { data = CodeModelItem(); data.referenceCount = (uint)-1; data.uKind = (uint)-1; } static bool isFree(const CodeModelItem& m_data) { return !m_data.id.isValid(); } static const CodeModelItem& data(const CodeModelItem& m_data) { return m_data; } static bool equals(const CodeModelItem& m_data, const CodeModelItem& rhs) { return m_data.id == rhs.id; } }; - -static void testReferenceCounting(const KDevelop::IndexedString& file) -{ -#ifdef TEST_REFERENCE_COUNTING_2 - uint count = 0; - const CodeModelItem* i; - this->items(file, count, i); - for(int a = 0; a < count; ++a) - Q_ASSERT(i[a].id.hasReferenceCount()); -#endif -} - DEFINE_LIST_MEMBER_HASH(CodeModelRepositoryItem, items, CodeModelItem) class CodeModelRepositoryItem { public: CodeModelRepositoryItem() : centralFreeItem(-1) { initializeAppendedLists(); } CodeModelRepositoryItem(const CodeModelRepositoryItem& rhs, bool dynamic = true) : file(rhs.file), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~CodeModelRepositoryItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return file.index(); } uint itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(CodeModelRepositoryItem); } IndexedString file; int centralFreeItem; START_APPENDED_LISTS(CodeModelRepositoryItem); APPENDED_LIST_FIRST(CodeModelRepositoryItem, CodeModelItem, items); END_APPENDED_LISTS(CodeModelRepositoryItem, items); }; class CodeModelRequestItem { public: CodeModelRequestItem(const CodeModelRepositoryItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(CodeModelRepositoryItem* item) const { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) CodeModelRepositoryItem(m_item, false); } static void destroy(CodeModelRepositoryItem* item, KDevelop::AbstractItemRepository&) { Q_ASSERT(shouldDoDUChainReferenceCounting(item)); // Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); item->~CodeModelRepositoryItem(); } static bool persistent(const CodeModelRepositoryItem* item) { Q_UNUSED(item); return true; } bool equals(const CodeModelRepositoryItem* item) const { return m_item.file == item->file; } const CodeModelRepositoryItem& m_item; }; class CodeModelPrivate { public: CodeModelPrivate() : m_repository(QStringLiteral("Code Model")) { } //Maps declaration-ids to items ItemRepository m_repository; }; CodeModel::CodeModel() : d(new CodeModelPrivate()) { } CodeModel::~CodeModel() { delete d; } void CodeModel::addItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << "addItem" << file.str() << id.identifier().toString() << id.index; ) if(!id.isValid()) return; CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; - testReferenceCounting(file); - if(index) { const CodeModelRepositoryItem* oldItem = d->m_repository.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); QMutexLocker lock(d->m_repository.mutex()); DynamicItem editableItem = d->m_repository.dynamicItemFromIndex(index); CodeModelItem* items = const_cast(editableItem->items()); if(listIndex != -1) { //Only update the reference-count ++items[listIndex].referenceCount; items[listIndex].kind = kind; return; }else{ //Add the item to the list EmbeddedTreeAddItem add(items, editableItem->itemsSize(), editableItem->centralFreeItem, newItem); if(add.newItemCount() != editableItem->itemsSize()) { //The data needs to be transferred into a bigger list. That list is within "item". item.itemsList().resize(add.newItemCount()); add.transferData(item.itemsList().data(), item.itemsList().size(), &item.centralFreeItem); d->m_repository.deleteItem(index); }else{ //We're fine: The item fits into the existing list. - testReferenceCounting(file); return; } } }else{ //We're creating a new index item.itemsList().append(newItem); } - testReferenceCounting(file); - Q_ASSERT(!d->m_repository.findIndex(request)); //This inserts the changed item volatile uint newIndex = d->m_repository.index(request); Q_UNUSED(newIndex); ifDebug( qCDebug(LANGUAGE) << "new index" << newIndex; ) - testReferenceCounting(file); - Q_ASSERT(d->m_repository.findIndex(request)); } void CodeModel::updateItem(const IndexedString& file, const IndexedQualifiedIdentifier& id, CodeModelItem::Kind kind) { ifDebug( qCDebug(LANGUAGE) << file.str() << id.identifier().toString() << kind; ) if(!id.isValid()) return; - testReferenceCounting(file); - CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); CodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.referenceCount = 1; uint index = d->m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); Q_ASSERT(listIndex != -1); CodeModelItem* items = const_cast(oldItem->items()); Q_ASSERT(items[listIndex].id == id); items[listIndex].kind = kind; - testReferenceCounting(file); - return; } Q_ASSERT(0); //The updated item as not in the symbol table! } void CodeModel::removeItem(const IndexedString& file, const IndexedQualifiedIdentifier& id) //void CodeModel::removeDeclaration(const QualifiedIdentifier& id, const IndexedDeclaration& declaration) { if(!id.isValid()) return; - testReferenceCounting(file); ifDebug( qCDebug(LANGUAGE) << "removeItem" << file.str() << id.identifier().toString(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { CodeModelItem searchItem; searchItem.id = id; QMutexLocker lock(d->m_repository.mutex()); DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(searchItem); if(listIndex == -1) return; CodeModelItem* items = const_cast(oldItem->items()); --items[listIndex].referenceCount; if(oldItem->items()[listIndex].referenceCount) return; //Nothing to remove, there's still a reference-count left //We have reduced the reference-count to zero, so remove the item from the list EmbeddedTreeRemoveItem remove(items, oldItem->itemsSize(), oldItem->centralFreeItem, searchItem); uint newItemCount = remove.newItemCount(); if(newItemCount != oldItem->itemsSize()) { if(newItemCount == 0) { //Has become empty, delete the item d->m_repository.deleteItem(index); - testReferenceCounting(file); return; }else{ //Make smaller item.itemsList().resize(newItemCount); remove.transferData(item.itemsList().data(), item.itemsSize(), &item.centralFreeItem); //Delete the old list d->m_repository.deleteItem(index); //Add the new list d->m_repository.index(request); - testReferenceCounting(file); return; } } } - testReferenceCounting(file); } void CodeModel::items(const IndexedString& file, uint& count, const CodeModelItem*& items) const { ifDebug( qCDebug(LANGUAGE) << "items" << file.str(); ) CodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { const CodeModelRepositoryItem* repositoryItem = d->m_repository.itemFromIndex(index); ifDebug( qCDebug(LANGUAGE) << "found index" << index << repositoryItem->itemsSize(); ) count = repositoryItem->itemsSize(); items = repositoryItem->items(); }else{ ifDebug( qCDebug(LANGUAGE) << "found no index"; ) count = 0; items = 0; } } CodeModel& CodeModel::self() { static CodeModel ret; return ret; } } diff --git a/language/duchain/duchainlock.cpp b/language/duchain/duchainlock.cpp index 4aa321f47..6b2413a17 100644 --- a/language/duchain/duchainlock.cpp +++ b/language/duchain/duchainlock.cpp @@ -1,271 +1,271 @@ /* This file is part of KDevelop Copyright 2007 Kris Wong Copyright 2007 Hamish Rodda Copyright 2007-2009 David Nolden Copyright 2013 Milian Wolff 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 "duchainlock.h" #include "duchain.h" #include #include #include ///@todo Always prefer exactly that lock that is requested by the thread that has the foreground mutex, /// to reduce the amount of UI blocking. -//Nanoseconds to sleep when waiting for a lock +//Microseconds to sleep when waiting for a lock const uint uSleepTime = 500; namespace KDevelop { class DUChainLockPrivate { public: DUChainLockPrivate() : m_writer(0) , m_writerRecursion(0) , m_totalReaderRecursion(0) { } int ownReaderRecursion() const { return m_readerRecursion.localData(); } void changeOwnReaderRecursion(int difference) { m_readerRecursion.localData() += difference; Q_ASSERT(m_readerRecursion.localData() >= 0); m_totalReaderRecursion.fetchAndAddOrdered(difference); } ///Holds the writer that currently has the write-lock, or zero. Is protected by m_writerRecursion. QAtomicPointer m_writer; ///How often is the chain write-locked by the writer? This value protects m_writer, ///m_writer may only be changed by the thread that successfully increases this value from 0 to 1 QAtomicInt m_writerRecursion; ///How often is the chain read-locked recursively by all readers? Should be sum of all m_readerRecursion values QAtomicInt m_totalReaderRecursion; QThreadStorage m_readerRecursion; }; DUChainLock::DUChainLock() : d(new DUChainLockPrivate) { } DUChainLock::~DUChainLock() { delete d; } bool DUChainLock::lockForRead(unsigned int timeout) { ///Step 1: Increase the own reader-recursion. This will make sure no further write-locks will succeed d->changeOwnReaderRecursion(1); QThread* w = d->m_writer.loadAcquire(); if (w == 0 || w == QThread::currentThread()) { //Successful lock: Either there is no writer, or we hold the write-lock by ourselves } else { ///Step 2: Start spinning until there is no writer any more QElapsedTimer t; if (timeout) { t.start(); } while (d->m_writer.loadAcquire()) { if (!timeout || t.elapsed() < timeout) { QThread::usleep(uSleepTime); } else { //Fail! d->changeOwnReaderRecursion(-1); return false; } } } return true; } void DUChainLock::releaseReadLock() { d->changeOwnReaderRecursion(-1); } bool DUChainLock::currentThreadHasReadLock() { return (bool)d->ownReaderRecursion(); } bool DUChainLock::lockForWrite(uint timeout) { //It is not allowed to acquire a write-lock while holding read-lock Q_ASSERT(d->ownReaderRecursion() == 0); if (d->m_writer.load() == QThread::currentThread()) { //We already hold the write lock, just increase the recursion count and return d->m_writerRecursion.fetchAndAddRelaxed(1); return true; } QElapsedTimer t; if (timeout) { t.start(); } while (1) { //Try acquiring the write-lcok if (d->m_totalReaderRecursion.load() == 0 && d->m_writerRecursion.testAndSetOrdered(0, 1)) { //Now we can be sure that there is no other writer, as we have increased m_writerRecursion from 0 to 1 d->m_writer = QThread::currentThread(); if (d->m_totalReaderRecursion.load() == 0) { //There is still no readers, we have successfully acquired a write-lock return true; } else { //There may be readers.. we have to continue spinning d->m_writer = 0; d->m_writerRecursion = 0; } } if (!timeout || t.elapsed() < timeout) { QThread::usleep(uSleepTime); } else { //Fail! return false; } } return false; } void DUChainLock::releaseWriteLock() { Q_ASSERT(currentThreadHasWriteLock()); //The order is important here, m_writerRecursion protects m_writer //TODO: could testAndSet here if (d->m_writerRecursion.load() == 1) { d->m_writer = 0; d->m_writerRecursion = 0; } else { d->m_writerRecursion.fetchAndAddOrdered(-1); } } bool DUChainLock::currentThreadHasWriteLock() { return d->m_writer.load() == QThread::currentThread(); } DUChainReadLocker::DUChainReadLocker(DUChainLock* duChainLock, uint timeout) : m_lock(duChainLock ? duChainLock : DUChain::lock()) , m_locked(false) , m_timeout(timeout) { lock(); } DUChainReadLocker::~DUChainReadLocker() { unlock(); } bool DUChainReadLocker::locked() const { return m_locked; } bool DUChainReadLocker::lock() { if (m_locked) { return true; } bool l = false; if (m_lock) { l = m_lock->lockForRead(m_timeout); Q_ASSERT(m_timeout || l); }; m_locked = l; return l; } void DUChainReadLocker::unlock() { if (m_locked && m_lock) { m_lock->releaseReadLock(); m_locked = false; } } DUChainWriteLocker::DUChainWriteLocker(DUChainLock* duChainLock, uint timeout) : m_lock(duChainLock ? duChainLock : DUChain::lock()) , m_locked(false) , m_timeout(timeout) { lock(); } DUChainWriteLocker::~DUChainWriteLocker() { unlock(); } bool DUChainWriteLocker::lock() { if (m_locked) { return true; } bool l = false; if (m_lock) { l = m_lock->lockForWrite(m_timeout); Q_ASSERT(m_timeout || l); }; m_locked = l; return l; } bool DUChainWriteLocker::locked() const { return m_locked; } void DUChainWriteLocker::unlock() { if (m_locked && m_lock) { m_lock->releaseWriteLock(); m_locked = false; } } } diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 6a0d306d7..677a533ef 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,222 +1,225 @@ /* Copyright 2009 David Nolden 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 "problemnavigationcontext.h" #include #include #include "util/debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString KEY_INVOKE_ACTION(int num) { return QString("invoke_action_%1").arg(num); } QString iconForSeverity(IProblem::Severity severity) { switch (severity) { case IProblem::Hint: return QStringLiteral("dialog-information"); case IProblem::Warning: return QStringLiteral("dialog-warning"); case IProblem::Error: return QStringLiteral("dialog-error"); + case IProblem::NoSeverity: + return {}; } + Q_UNREACHABLE(); return {}; } QString htmlImg(const QString& iconName, KIconLoader::Group group) { KIconLoader loader; const int size = loader.currentSize(group); return QString::fromLatin1("") .arg(size) .arg(loader.iconPath(iconName, group)); } } ProblemNavigationContext::ProblemNavigationContext(const IProblem::Ptr& problem, const Flags flags) : m_problem(problem) , m_flags(flags) , m_widget(nullptr) { } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; auto iconPath = iconForSeverity(m_problem->severity()); modifyHtml() += QStringLiteral(""); modifyHtml() += QStringLiteral("").arg(htmlImg(iconPath, KIconLoader::Panel)); // BEGIN: right column modifyHtml() += QStringLiteral(""); // END: right column modifyHtml() += QStringLiteral("
%1"); modifyHtml() += i18n("Problem in %1", m_problem->sourceString()); modifyHtml() += QStringLiteral("
"); if (m_flags & ShowLocation) { const auto duchainProblem = dynamic_cast(m_problem.data()); if (duchainProblem) { modifyHtml() += labelHighlight(i18n("Location: ")); makeLink(QStringLiteral("%1 :%2") .arg(duchainProblem->finalLocation().document.toUrl().fileName()) .arg(duchainProblem->rangeInCurrentRevision().start().line() + 1), QString(), NavigationAction(duchainProblem->finalLocation().document.toUrl(), duchainProblem->finalLocation().start()) ); modifyHtml() += QStringLiteral("
"); } } modifyHtml() += m_problem->description().toHtmlEscaped(); if ( !m_problem->explanation().isEmpty() ) { modifyHtml() += "

" + m_problem->explanation().toHtmlEscaped() + "

"; } modifyHtml() += QStringLiteral("
"); const QVector diagnostics = m_problem->diagnostics(); if (!diagnostics.isEmpty()) { DUChainReadLocker lock; for (auto diagnostic : diagnostics) { modifyHtml() += QStringLiteral("

"); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()); if (declaration) { modifyHtml() += i18n("
See: "); makeLink(declaration->toString(), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2") .arg(declaration->url().toUrl().fileName()) .arg(declaration->rangeInCurrentRevision().start().line() + 1), DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } else if (range.start().isValid()) { modifyHtml() += i18n("
See: "); const auto url = range.document.toUrl(); makeLink(QStringLiteral("%1 :%2") .arg(url.fileName()) .arg(range.start().line() + 1), url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start())); } modifyHtml() += QStringLiteral("

"); } } if (!m_cachedAssistant) { m_cachedAssistant = m_problem->solutionAssistant(); } auto assistant = m_cachedAssistant; if (assistant && !assistant->actions().isEmpty()) { modifyHtml() += QString::fromLatin1("").arg("#b3d4ff"); modifyHtml() += QStringLiteral(""; modifyHtml() += QStringLiteral("
%1").arg(htmlImg(QStringLiteral("dialog-ok-apply"), KIconLoader::Panel)); int index = 0; foreach (auto assistantAction, assistant->actions()) { if (index != 0) { modifyHtml() += "
"; } makeLink(i18n("Solution (%1)", index + 1), KEY_INVOKE_ACTION(index), NavigationAction(KEY_INVOKE_ACTION(index))); modifyHtml() += ": " + assistantAction->description().toHtmlEscaped(); ++index; } modifyHtml() += "
"); } return currentHtml(); } NavigationContextPointer ProblemNavigationContext::executeKeyAction(QString key) { auto assistant = m_cachedAssistant; if (!assistant) return {}; if (key.startsWith(QLatin1String("invoke_action_"))) { const auto index = key.replace(QLatin1String("invoke_action_"), QString()).toInt(); executeAction(index); } return {}; } void ProblemNavigationContext::executeAction(int index) { auto assistant = m_problem->solutionAssistant(); if (!assistant) return; auto action = assistant->actions().value(index); if (action) { action->execute(); if ( topContext() ) { DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate); } } else { qCWarning(LANGUAGE()) << "No such action"; return; } }