File indexing completed on 2024-05-12 04:37:35

0001 /*
0002     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "framestackmodel.h"
0008 
0009 #include <QFileInfo>
0010 #include <QIcon>
0011 #include <QMimeType>
0012 #include <QMimeDatabase>
0013 
0014 #include <KLocalizedString>
0015 #include <KColorScheme>
0016 
0017 #include "../../interfaces/icore.h"
0018 #include "../../interfaces/iprojectcontroller.h"
0019 #include "../interfaces/isession.h"
0020 #include <debug.h>
0021 
0022 namespace KDevelop {
0023 
0024 class FrameStackModelPrivate
0025 {
0026 public:
0027     explicit FrameStackModelPrivate(FrameStackModel* q) : q(q) {}
0028 
0029     void update();
0030 
0031     QModelIndex indexForThreadNumber(int threadNumber);
0032 
0033     FrameStackModel* q;
0034 
0035     int m_currentThread = -1;
0036     int m_currentFrame = -1;
0037 
0038     int m_crashedThreadIndex = -1;
0039 
0040     // used to count how often a user has scrolled down and more frames needed to be fetched;
0041     // this way, the number of frames fetched in each chunk can be increased if the user wants
0042     // to scroll far
0043     int m_subsequentFrameFetchOperations = 0;
0044     bool m_updateCurrentFrameOnNextFetch = false;
0045 
0046     QVector<FrameStackModel::ThreadItem> m_threads;
0047     QHash<int, QVector<FrameStackModel::FrameItem>> m_frames;
0048     QHash<int, bool> m_hasMoreFrames;
0049 
0050     // Caches
0051     mutable QHash<QString, bool> m_fileExistsCache;
0052 };
0053 
0054 FrameStackModel::FrameStackModel(IDebugSession *session)
0055     : IFrameStackModel(session)
0056     , d_ptr(new FrameStackModelPrivate(this))
0057 {
0058     connect(session, &IDebugSession::stateChanged, this, &FrameStackModel::stateChanged);
0059 }
0060 
0061 FrameStackModel::~FrameStackModel()
0062 {
0063 }
0064 
0065 void FrameStackModel::setThreads(const QVector<ThreadItem>& threads)
0066 {
0067     Q_D(FrameStackModel);
0068 
0069     qCDebug(DEBUGGER) << threads.count();
0070 
0071     if (!d->m_threads.isEmpty()) {
0072         beginRemoveRows(QModelIndex(), 0, d->m_threads.count()-1);
0073         d->m_threads.clear();
0074         endRemoveRows();
0075     }
0076 
0077     if (!threads.isEmpty()) {
0078         beginInsertRows(QModelIndex(), 0, threads.count()-1);
0079         d->m_threads = threads;
0080         endInsertRows();
0081     }
0082 }
0083 
0084 QModelIndex FrameStackModelPrivate::indexForThreadNumber(int threadNumber)
0085 {
0086     int i=0;
0087     for (const auto& t : qAsConst(m_threads)) {
0088         if (t.nr == threadNumber) {
0089             return q->index(i, 0);
0090         }
0091         i++;
0092     }
0093     return QModelIndex();
0094 }
0095 
0096 void FrameStackModel::setFrames(int threadNumber, const QVector<FrameItem>& frames)
0097 {
0098     Q_D(FrameStackModel);
0099 
0100     QModelIndex threadIndex = d->indexForThreadNumber(threadNumber);
0101     Q_ASSERT(threadIndex.isValid());
0102 
0103     auto& threadFrames = d->m_frames[threadNumber];
0104     if (!threadFrames.empty()) {
0105         beginRemoveRows(threadIndex, 0, threadFrames.size() - 1);
0106         threadFrames.clear();
0107         endRemoveRows();
0108     }
0109 
0110     if (!frames.isEmpty()) {
0111         beginInsertRows(threadIndex, 0, frames.count()-1);
0112         threadFrames = frames;
0113         endInsertRows();
0114     }
0115 
0116     if (d->m_currentThread == threadNumber && d->m_updateCurrentFrameOnNextFetch) {
0117         d->m_currentFrame = 0;
0118         d->m_updateCurrentFrameOnNextFetch = false;
0119     }
0120 
0121     d->m_fileExistsCache.clear();
0122 
0123     session()->raiseEvent(IDebugSession::thread_or_frame_changed);
0124 
0125     // FIXME: Ugly hack. Apparently, when rows are added, the selection
0126     // in the view is cleared. Emit this so that some frame is still
0127     // selected.
0128     emit currentFrameChanged(d->m_currentFrame);
0129 }
0130 
0131 void FrameStackModel::insertFrames(int threadNumber, const QVector<FrameItem>& frames)
0132 {
0133     Q_D(FrameStackModel);
0134 
0135     QModelIndex threadIndex = d->indexForThreadNumber(threadNumber);
0136     Q_ASSERT(threadIndex.isValid());
0137 
0138     auto& threadFrames = d->m_frames[threadNumber];
0139     beginInsertRows(threadIndex, threadFrames.size() - 1, threadFrames.size() + frames.size() - 1);
0140     threadFrames << frames;
0141     endInsertRows();
0142 }
0143 
0144 void FrameStackModel::setHasMoreFrames(int threadNumber, bool hasMoreFrames)
0145 {
0146     Q_D(FrameStackModel);
0147 
0148     d->m_hasMoreFrames[threadNumber] = hasMoreFrames;
0149 }
0150 
0151 FrameStackModel::FrameItem FrameStackModel::frame(const QModelIndex& index)
0152 {
0153     Q_D(FrameStackModel);
0154 
0155     Q_ASSERT(index.internalId());
0156     Q_ASSERT(static_cast<quintptr>(d->m_threads.count()) >= index.internalId());
0157     const ThreadItem &thread = d->m_threads.at(index.internalId()-1);
0158     return d->m_frames[thread.nr].at(index.row());
0159 }
0160 
0161 QVector<FrameStackModel::FrameItem> FrameStackModel::frames(int threadNumber) const
0162 {
0163     Q_D(const FrameStackModel);
0164 
0165     return d->m_frames.value(threadNumber);
0166 }
0167 
0168 QVariant FrameStackModel::data(const QModelIndex& index, int role) const
0169 {
0170     Q_D(const FrameStackModel);
0171 
0172     if (!index.internalId()) {
0173         //thread
0174         if (d->m_threads.count() <= index.row()) return QVariant();
0175         const ThreadItem &thread = d->m_threads.at(index.row());
0176         if (index.column() == 0) {
0177             if (role == Qt::DisplayRole) {
0178                 if (thread.nr == d->m_crashedThreadIndex) {
0179                     return i18nc("#thread-id at function-name or address", "#%1 at %2 (crashed)", thread.nr, thread.name);
0180                 } else {
0181                     return i18nc("#thread-id at function-name or address", "#%1 at %2", thread.nr, thread.name);
0182                 }
0183             } else if (role == Qt::ForegroundRole) {
0184                 if (thread.nr == d->m_crashedThreadIndex) {
0185                     KColorScheme scheme(QPalette::Active);
0186                     return scheme.foreground(KColorScheme::NegativeText).color();
0187                 }
0188             }
0189         }
0190     } else {
0191         //frame
0192         if (static_cast<quintptr>(d->m_threads.count()) < index.internalId()) return QVariant();
0193         const ThreadItem &thread = d->m_threads.at(index.internalId()-1);
0194         const auto& threadFrames = d->m_frames[thread.nr];
0195         if (index.row() >= threadFrames.size())
0196             return QVariant();
0197         const FrameItem& frame = threadFrames.at(index.row());
0198         if (index.column() == 0) {
0199             if (role == Qt::DisplayRole) {
0200                 return QVariant(QString::number(frame.nr));
0201             }
0202         } else if (index.column() == 1) {
0203             if (role == Qt::DisplayRole) {
0204                 return QVariant(frame.name);
0205             }
0206         } else if (index.column() == 2) {
0207             if (role == Qt::DisplayRole) {
0208                 QString ret = ICore::self()->projectController()
0209                     ->prettyFileName(frame.file, IProjectController::FormatPlain);
0210                 if (frame.line != -1) {
0211                     ret += QLatin1Char(':') + QString::number(frame.line + 1);
0212                 }
0213                 return ret;
0214             } else if (role == Qt::DecorationRole) {
0215                 QMimeType mime = QMimeDatabase().mimeTypeForUrl(frame.file);
0216                 return QIcon::fromTheme(mime.iconName());
0217             } else if (role == Qt::ForegroundRole) {
0218                 const auto fileName = frame.file.toLocalFile();
0219                 auto cacheIt = d->m_fileExistsCache.find(fileName);
0220                 if (cacheIt == d->m_fileExistsCache.end()) {
0221                     cacheIt = d->m_fileExistsCache.insert(fileName, QFileInfo::exists(fileName));
0222                 }
0223                 const bool fileExists = cacheIt.value();
0224                 if (!fileExists) {
0225                     KColorScheme scheme(QPalette::Active);
0226                     return scheme.foreground(KColorScheme::InactiveText).color();
0227                 }
0228             }
0229         }
0230     }
0231     return QVariant();
0232 }
0233 
0234 int FrameStackModel::columnCount(const QModelIndex& parent) const
0235 {
0236     Q_UNUSED(parent);
0237     return 3;
0238 }
0239 
0240 int FrameStackModel::rowCount(const QModelIndex& parent) const
0241 {
0242     Q_D(const FrameStackModel);
0243 
0244     if (!parent.isValid()) {
0245         return d->m_threads.count();
0246     } else if (!parent.internalId() && parent.column() == 0) {
0247         if (parent.row() < d->m_threads.count()) {
0248             return d->m_frames[d->m_threads.at(parent.row()).nr].count();
0249         }
0250     }
0251     return 0;
0252 }
0253 
0254 QModelIndex FrameStackModel::parent(const QModelIndex& child) const
0255 {
0256     if (!child.internalId()) {
0257         return QModelIndex();
0258     } else {
0259         return index(child.internalId()-1, 0);
0260     }
0261 }
0262 
0263 QModelIndex FrameStackModel::index(int row, int column, const QModelIndex& parent) const
0264 {
0265     Q_D(const FrameStackModel);
0266 
0267     if (parent.isValid()) {
0268         Q_ASSERT(!parent.internalId());
0269         Q_ASSERT(parent.column() == 0);
0270         Q_ASSERT(parent.row() < d->m_threads.count());
0271         return createIndex(row, column, parent.row()+1);
0272     } else {
0273         return createIndex(row, column);
0274     }
0275 }
0276 
0277 
0278 QVariant FrameStackModel::headerData(int section, Qt::Orientation orientation, int role) const
0279 {
0280     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0281         if (section == 0) {
0282             return i18n("Depth");
0283         } else if (section == 1) {
0284             return i18n("Function");
0285         } else if (section == 2) {
0286             return i18n("Source");
0287         }
0288     }
0289     return QAbstractItemModel::headerData(section, orientation, role);
0290 }
0291 
0292 void FrameStackModel::setCurrentThread(int threadNumber)
0293 {
0294     Q_D(FrameStackModel);
0295 
0296     qCDebug(DEBUGGER) << threadNumber;
0297     if (d->m_currentThread != threadNumber && threadNumber != -1) {
0298         // FIXME: this logic means that if we switch to thread 3 and
0299         // then to thread 2 and then to thread 3, we'll request frames
0300         // for thread 3 again, even if the program was not run in between
0301         // and therefore frames could not have changed.
0302         d->m_currentFrame = 0; //set before fetchFrames else --frame argument would be wrong
0303         d->m_updateCurrentFrameOnNextFetch = true;
0304         fetchFrames(threadNumber, 0, 20);
0305     }
0306     if (threadNumber != d->m_currentThread) {
0307         d->m_currentFrame = 0;
0308         d->m_currentThread = threadNumber;
0309         emit currentFrameChanged(d->m_currentFrame);
0310     }
0311     qCDebug(DEBUGGER) << "currentThread: " << d->m_currentThread << "currentFrame: " << d->m_currentFrame;
0312     emit currentThreadChanged(threadNumber);
0313     session()->raiseEvent(IDebugSession::thread_or_frame_changed);
0314 }
0315 
0316 void FrameStackModel::setCurrentThread(const QModelIndex& index)
0317 {
0318     Q_D(const FrameStackModel);
0319 
0320     Q_ASSERT(index.isValid());
0321     Q_ASSERT(!index.internalId());
0322     Q_ASSERT(index.column() == 0);
0323     setCurrentThread(d->m_threads[index.row()].nr);
0324 }
0325 
0326 void FrameStackModel::setCrashedThreadIndex(int index)
0327 {
0328     Q_D(FrameStackModel);
0329 
0330     d->m_crashedThreadIndex = index;
0331 }
0332 
0333 int FrameStackModel::crashedThreadIndex() const
0334 {
0335     Q_D(const FrameStackModel);
0336 
0337     return d->m_crashedThreadIndex;
0338 }
0339 
0340 int FrameStackModel::currentThread() const
0341 {
0342     Q_D(const FrameStackModel);
0343 
0344     return d->m_currentThread;
0345 }
0346 
0347 QModelIndex FrameStackModel::currentThreadIndex() const
0348 {
0349     Q_D(const FrameStackModel);
0350 
0351     int i = 0;
0352     for (const ThreadItem& t : qAsConst(d->m_threads)) {
0353         if (t.nr == currentThread()) {
0354             return index(i, 0);
0355         }
0356         ++i;
0357     }
0358     return QModelIndex();
0359 }
0360 
0361 int FrameStackModel::currentFrame() const
0362 {
0363     Q_D(const FrameStackModel);
0364 
0365     return d->m_currentFrame;
0366 }
0367 
0368 QModelIndex FrameStackModel::currentFrameIndex() const
0369 {
0370     Q_D(const FrameStackModel);
0371 
0372     return index(d->m_currentFrame, 0, currentThreadIndex());
0373 }
0374 
0375 void FrameStackModel::setCurrentFrame(int frame)
0376 {
0377     Q_D(FrameStackModel);
0378 
0379     qCDebug(DEBUGGER) << frame;
0380     if (frame != d->m_currentFrame)
0381     {
0382         d->m_currentFrame = frame;
0383         session()->raiseEvent(IDebugSession::thread_or_frame_changed);
0384         emit currentFrameChanged(frame);
0385     }
0386 }
0387 
0388 void FrameStackModelPrivate::update()
0389 {
0390     m_subsequentFrameFetchOperations = 0;
0391     q->fetchThreads();
0392     if (m_currentThread != -1) {
0393         q->fetchFrames(m_currentThread, 0, 20);
0394     }
0395 }
0396 
0397 void FrameStackModel::handleEvent(IDebugSession::event_t event)
0398 {
0399     Q_D(FrameStackModel);
0400 
0401     switch (event)
0402     {
0403     case IDebugSession::program_state_changed:
0404         d->update();
0405         break;
0406 
0407     default:
0408         break;
0409     }
0410 }
0411 
0412 void FrameStackModel::stateChanged(IDebugSession::DebuggerState state)
0413 {
0414     Q_D(FrameStackModel);
0415 
0416     if (state == IDebugSession::PausedState) {
0417         setCurrentFrame(-1);
0418         d->m_updateCurrentFrameOnNextFetch = true;
0419     } else if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) {
0420         setThreads(QVector<FrameStackModel::ThreadItem>());
0421     }
0422 }
0423 
0424 // FIXME: it should be possible to fetch more frames for
0425 // an arbitrary thread, without making it current.
0426 void FrameStackModel::fetchMoreFrames()
0427 {
0428     Q_D(FrameStackModel);
0429 
0430     d->m_subsequentFrameFetchOperations += 1;
0431     const int fetch = 20 * d->m_subsequentFrameFetchOperations * d->m_subsequentFrameFetchOperations;
0432     if (d->m_currentThread != -1 && d->m_hasMoreFrames[d->m_currentThread]) {
0433         setHasMoreFrames(d->m_currentThread, false);
0434         const int frameCount = d->m_frames[d->m_currentThread].size();
0435         fetchFrames(d->m_currentThread, frameCount, frameCount - 1 + fetch);
0436     }
0437 }
0438 
0439 }
0440 
0441 #include "moc_framestackmodel.cpp"