forked from qt-creator/qt-creator
CppEditor: Let users check for unused functions in (sub-)projects
Note that especially in C++, there can be a lot of false positives, especially in template-heavy code bases. We filter out the most notorious offenders, namely: - templates themselves - constructors and destructors - *begin() and *end() - qHash() - main() Since the code model does not know about symbol visibility, the functionality is quite useless for libraries, unless you want to check your test coverage. The procedure is rather slow, but that shouldn't matter so much, as it's something you'll only run "once in a while". Fixes: QTCREATORBUG-6772 Change-Id: If00a537b760a9b0babdda6c848133715c3240155 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -26,12 +26,15 @@
|
||||
#include "symbolfinder.h"
|
||||
#include "symbolsfindfilter.h"
|
||||
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/documentmanager.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/find/searchresultwindow.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/jsexpander.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <coreplugin/progressmanager/futureprogress.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
#include <coreplugin/vcsmanager.h>
|
||||
#include <cplusplus/ASTPath.h>
|
||||
@@ -59,9 +62,11 @@
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/runextensions.h>
|
||||
#include <utils/savefile.h>
|
||||
#include <utils/temporarydirectory.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -72,10 +77,13 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QTextBlock>
|
||||
#include <QThread>
|
||||
#include <QThreadPool>
|
||||
#include <QTimer>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
|
||||
#define WITH_AST_DUMP
|
||||
#include <iostream>
|
||||
@@ -456,6 +464,179 @@ void CppModelManager::showPreprocessedFile(bool inNextSplit)
|
||||
compiler->start();
|
||||
}
|
||||
|
||||
class FindUnusedActionsEnabledSwitcher
|
||||
{
|
||||
public:
|
||||
FindUnusedActionsEnabledSwitcher()
|
||||
: actions{Core::ActionManager::command("CppTools.FindUnusedFunctions"),
|
||||
Core::ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")}
|
||||
{
|
||||
for (Core::Command * const action : actions)
|
||||
action->action()->setEnabled(false);
|
||||
}
|
||||
~FindUnusedActionsEnabledSwitcher()
|
||||
{
|
||||
for (Core::Command * const action : actions)
|
||||
action->action()->setEnabled(true);
|
||||
}
|
||||
private:
|
||||
const QList<Core::Command *> actions;
|
||||
};
|
||||
using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr<FindUnusedActionsEnabledSwitcher>;
|
||||
|
||||
static void checkNextFunctionForUnused(
|
||||
const QPointer<Core::SearchResult> &search,
|
||||
const std::shared_ptr<QFutureInterface<bool>> &findRefsFuture,
|
||||
const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher)
|
||||
{
|
||||
if (!search || findRefsFuture->isCanceled())
|
||||
return;
|
||||
QVariantMap data = search->userData().toMap();
|
||||
QVariant &remainingLinks = data["remaining"];
|
||||
QVariantList remainingLinksList = remainingLinks.toList();
|
||||
QVariant &activeLinks = data["active"];
|
||||
QVariantList activeLinksList = activeLinks.toList();
|
||||
if (remainingLinksList.isEmpty()) {
|
||||
if (activeLinksList.isEmpty()) {
|
||||
search->finishSearch(false);
|
||||
findRefsFuture->reportFinished();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto link = qvariant_cast<Link>(remainingLinksList.takeFirst());
|
||||
activeLinksList << QVariant::fromValue(link);
|
||||
remainingLinks = remainingLinksList;
|
||||
activeLinks = activeLinksList;
|
||||
search->setUserData(data);
|
||||
CppModelManager::instance()->modelManagerSupport(CppModelManager::Backend::Best)
|
||||
->checkUnused(link, search, [search, link, findRefsFuture, actionsSwitcher](const Link &) {
|
||||
if (!search || findRefsFuture->isCanceled())
|
||||
return;
|
||||
const int newProgress = findRefsFuture->progressValue() + 1;
|
||||
findRefsFuture->setProgressValueAndText(newProgress, Tr::tr("Checked %1 of %2 functions")
|
||||
.arg(newProgress).arg(findRefsFuture->progressMaximum()));
|
||||
QVariantMap data = search->userData().toMap();
|
||||
QVariant &activeLinks = data["active"];
|
||||
QVariantList activeLinksList = activeLinks.toList();
|
||||
QTC_CHECK(activeLinksList.removeOne(QVariant::fromValue(link)));
|
||||
activeLinks = activeLinksList;
|
||||
search->setUserData(data);
|
||||
checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher);
|
||||
});
|
||||
}
|
||||
|
||||
void CppModelManager::findUnusedFunctions(const FilePath &folder)
|
||||
{
|
||||
const auto actionsSwitcher = std::make_shared<FindUnusedActionsEnabledSwitcher>();
|
||||
|
||||
// Step 1: Employ locator to find all functions
|
||||
Core::ILocatorFilter *const functionsFilter
|
||||
= Utils::findOrDefault(Core::ILocatorFilter::allLocatorFilters(),
|
||||
Utils::equal(&Core::ILocatorFilter::id,
|
||||
Id(Constants::FUNCTIONS_FILTER_ID)));
|
||||
QTC_ASSERT(functionsFilter, return);
|
||||
const QPointer<Core::SearchResult> search
|
||||
= Core::SearchResultWindow::instance()
|
||||
->startNewSearch(tr("Find Unused Functions"),
|
||||
{},
|
||||
{},
|
||||
Core::SearchResultWindow::SearchOnly,
|
||||
Core::SearchResultWindow::PreserveCaseDisabled,
|
||||
"CppEditor");
|
||||
connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) {
|
||||
Core::EditorManager::openEditorAtSearchResult(item);
|
||||
});
|
||||
Core::SearchResultWindow::instance()->popup(Core::IOutputPane::ModeSwitch
|
||||
| Core::IOutputPane::WithFocus);
|
||||
const auto locatorWatcher = new QFutureWatcher<Core::LocatorFilterEntry>(search);
|
||||
functionsFilter->prepareSearch({});
|
||||
connect(search, &Core::SearchResult::canceled, locatorWatcher, [locatorWatcher] {
|
||||
locatorWatcher->cancel();
|
||||
});
|
||||
connect(locatorWatcher, &QFutureWatcher<Core::LocatorFilterEntry>::finished, search,
|
||||
[locatorWatcher, search, folder, actionsSwitcher] {
|
||||
locatorWatcher->deleteLater();
|
||||
if (locatorWatcher->isCanceled()) {
|
||||
search->finishSearch(true);
|
||||
return;
|
||||
}
|
||||
Links links;
|
||||
for (int i = 0; i < locatorWatcher->future().resultCount(); ++i) {
|
||||
const Core::LocatorFilterEntry &entry = locatorWatcher->resultAt(i);
|
||||
static const QStringList prefixBlacklist{"main(", "~", "qHash(", "begin()", "end()",
|
||||
"cbegin()", "cend()", "constBegin()", "constEnd()"};
|
||||
if (Utils::anyOf(prefixBlacklist, [&entry](const QString &prefix) {
|
||||
return entry.displayName.startsWith(prefix); })) {
|
||||
continue;
|
||||
}
|
||||
Link link;
|
||||
if (entry.internalData.canConvert<Link>()) {
|
||||
link = qvariant_cast<Link>(entry.internalData);
|
||||
} else {
|
||||
const auto item = qvariant_cast<IndexItem::Ptr>(entry.internalData);
|
||||
if (item) {
|
||||
link = Link(FilePath::fromString(item->fileName()), item->line(),
|
||||
item->column());
|
||||
}
|
||||
}
|
||||
if (link.hasValidTarget() && link.targetFilePath.isReadableFile()
|
||||
&& (folder.isEmpty() || link.targetFilePath.isChildOf(folder))
|
||||
&& SessionManager::projectForFile(link.targetFilePath)) {
|
||||
links << link;
|
||||
}
|
||||
}
|
||||
if (links.isEmpty()) {
|
||||
search->finishSearch(false);
|
||||
return;
|
||||
}
|
||||
QVariantMap remainingAndActiveLinks;
|
||||
remainingAndActiveLinks.insert("active", QVariantList());
|
||||
remainingAndActiveLinks.insert("remaining",
|
||||
Utils::transform<QVariantList>(links, [](const Link &l) { return QVariant::fromValue(l);
|
||||
}));
|
||||
search->setUserData(remainingAndActiveLinks);
|
||||
const auto findRefsFuture = std::make_shared<QFutureInterface<bool>>();
|
||||
Core::FutureProgress *const progress
|
||||
= Core::ProgressManager::addTask(findRefsFuture->future(),
|
||||
Tr::tr("Finding Unused Functions"),
|
||||
"CppEditor.FindUnusedFunctions");
|
||||
connect(progress,
|
||||
&Core::FutureProgress::canceled,
|
||||
search,
|
||||
[search, future = std::weak_ptr<QFutureInterface<bool>>(findRefsFuture)] {
|
||||
search->finishSearch(true);
|
||||
if (const auto f = future.lock()) {
|
||||
f->cancel();
|
||||
f->reportFinished();
|
||||
}
|
||||
});
|
||||
findRefsFuture->setProgressRange(0, links.size());
|
||||
connect(search, &Core::SearchResult::canceled, [findRefsFuture] {
|
||||
findRefsFuture->cancel();
|
||||
findRefsFuture->reportFinished();
|
||||
});
|
||||
|
||||
// Step 2: Forward search results one by one to backend to check which functions are unused.
|
||||
// We keep several requests in flight for decent throughput.
|
||||
const int inFlightCount = std::min(QThread::idealThreadCount() / 2 + 1, int(links.size()));
|
||||
for (int i = 0; i < inFlightCount; ++i)
|
||||
checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher);
|
||||
});
|
||||
locatorWatcher->setFuture(
|
||||
Utils::runAsync([functionsFilter](QFutureInterface<Core::LocatorFilterEntry> &future) {
|
||||
future.reportResults(functionsFilter->matchesFor(future, {}));
|
||||
}));
|
||||
}
|
||||
|
||||
void CppModelManager::checkForUnusedSymbol(Core::SearchResult *search,
|
||||
const Link &link,
|
||||
CPlusPlus::Symbol *symbol,
|
||||
const CPlusPlus::LookupContext &context,
|
||||
const LinkHandler &callback)
|
||||
{
|
||||
instance()->d->m_findReferences->checkUnused(search, link, symbol, context, callback);
|
||||
}
|
||||
|
||||
int argumentPositionOf(const AST *last, const CallAST *callAst)
|
||||
{
|
||||
if (!callAst || !callAst->expression_list)
|
||||
|
Reference in New Issue
Block a user