File indexing completed on 2024-05-12 05:43:31

0001 /*
0002     Copyright (C) 2015 Volker Krause <vkrause@kde.org>
0003 
0004     This program is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program.  If not, see <https://www.gnu.org/licenses/>.
0016 */
0017 
0018 #include "dependencymodel.h"
0019 
0020 #include <elf/elffileset.h>
0021 #include <elf/elffile.h>
0022 #include <elf/elfdynamicsection.h>
0023 #include <elf/elfsymboltablesection.h>
0024 #include <elf/elfhashsection.h>
0025 #include <checks/dependenciescheck.h>
0026 
0027 #include <QIcon>
0028 
0029 #include <cassert>
0030 #include <memory>
0031 
0032 DependencyModel::DependencyModel(QObject* parent): QAbstractItemModel(parent)
0033 {
0034 }
0035 
0036 DependencyModel::~DependencyModel()
0037 {
0038 }
0039 
0040 ElfFileSet* DependencyModel::fileSet() const
0041 {
0042     return m_fileSet;
0043 }
0044 
0045 void DependencyModel::setFileSet(ElfFileSet* fileSet)
0046 {
0047     beginResetModel();
0048     const auto l = [](DependencyModel* m) { m->endResetModel(); };
0049     const auto endReset = std::unique_ptr<DependencyModel, decltype(l)>(this, l);
0050 
0051     m_fileIndex.clear();
0052     m_childMap.clear();
0053     m_parentMap.clear();
0054     m_uniqueIndex = 0;
0055 
0056     m_fileSet = fileSet;
0057     if (!fileSet || fileSet->size() == 0)
0058         return;
0059 
0060     // build up indexes to make the tree building more efficient
0061     for (int i = 0; i < fileSet->size(); ++i) {
0062         const auto file = fileSet->file(i);
0063         if (!file->dynamicSection())
0064             continue;
0065         const auto soName = file->dynamicSection()->soName();
0066         if (!soName.isEmpty())
0067             m_fileIndex.insert(soName, i);
0068         else
0069             m_fileIndex.insert(file->fileName().toUtf8(), i);
0070     }
0071 
0072     // setup root
0073     m_parentMap.resize(1);
0074     m_parentMap[0] = 0;
0075     m_childMap.resize(1);
0076 
0077     // TODO identify top-level files (for now we just assume it's the first one)
0078 
0079     // fill the first level
0080     ++m_uniqueIndex;
0081     assert((uint32_t)m_parentMap.size() == m_uniqueIndex);
0082     assert((uint32_t)m_childMap.size() == m_uniqueIndex);
0083 
0084     m_parentMap.push_back(0);
0085     m_childMap[0].push_back(makeId(m_uniqueIndex, 0));
0086     m_childMap.push_back({});
0087 
0088     // clear symbol usage count table, will be filled on demand
0089     m_symbolCountTable.clear();
0090     m_symbolCountTable.resize(m_fileSet->size());
0091     for (int i = 0; i < m_fileSet->size(); ++i) {
0092         m_symbolCountTable[i].resize(m_fileSet->size());
0093         m_symbolCountTable[i].fill(-1, m_fileSet->size());
0094     }
0095 }
0096 
0097 QVariant DependencyModel::data(const QModelIndex& index, int role) const
0098 {
0099     if (!index.isValid() || !m_fileSet)
0100         return {};
0101 
0102     const auto file = fileIndex(index.internalId());
0103     const auto parentIdx = parent(index);
0104     const auto parentFile = fileIndex(parentIdx.internalId());
0105 
0106     switch (role) {
0107         case Qt::DisplayRole:
0108         {
0109             if (index.column() == 0) {
0110                 if (file != InvalidFile)
0111                     return m_fileSet->file(file)->displayName();
0112                 return m_fileSet->file(parentFile)->dynamicSection()->neededLibraries().at(index.row());
0113             }
0114 
0115             if (index.column() == 1 && file != InvalidFile && parentIdx.isValid()) {
0116                 auto count = m_symbolCountTable.at(parentFile).at(file);
0117                 if (count == -1)
0118                     count = m_symbolCountTable[parentFile][file] = usedSymbolCount(parentFile, file);
0119                 return count;
0120             }
0121 
0122             break;
0123         }
0124         case Qt::DecorationRole:
0125             if (index.column())
0126                 break;
0127             if (file == InvalidFile)
0128                 return QIcon::fromTheme(QStringLiteral("dialog-error"));
0129             if (hasCycle(index))
0130                 return QIcon::fromTheme(QStringLiteral("dialog-warning"));
0131             break;
0132         case Qt::ToolTipRole:
0133             if (hasCycle(index))
0134                 return tr("Cyclic dependency!");
0135             if (file != InvalidFile)
0136                 return m_fileSet->file(file)->fileName();
0137             else
0138                 return tr("Dependency not found!");
0139         case UserFileRole:
0140             if (parentFile == InvalidFile || !parentIdx.isValid())
0141                 return QVariant::fromValue<ElfFile*>(nullptr);
0142             return QVariant::fromValue(m_fileSet->file(parentFile));
0143         case ProviderFileRole:
0144             if (file == InvalidFile)
0145                 return QVariant::fromValue<ElfFile*>(nullptr);
0146             return QVariant::fromValue(m_fileSet->file(file));
0147     }
0148 
0149     return {};
0150 }
0151 
0152 int DependencyModel::columnCount(const QModelIndex& parent) const
0153 {
0154     Q_UNUSED(parent);
0155     return 2;
0156 }
0157 
0158 int DependencyModel::rowCount(const QModelIndex& parent) const
0159 {
0160     if (!m_fileSet || parent.column() > 0)
0161         return 0;
0162 
0163     const auto node = nodeId(parent.internalId());
0164     assert(node < (uint32_t)m_childMap.size());
0165 
0166     {
0167         // this reference will become invalid as soon as we modify m_childMap below!
0168         const auto &children = m_childMap.at(node);
0169         if (!children.isEmpty())
0170             return children.size();
0171     }
0172 
0173     const auto file = fileIndex(parent.internalId());
0174     if (file == InvalidFile || hasCycle(parent) || !m_fileSet->file(file)->dynamicSection())
0175         return 0;
0176 
0177     const auto needed = m_fileSet->file(file)->dynamicSection()->neededLibraries();
0178     if (needed.isEmpty())
0179         return 0;
0180 
0181     for (const auto &need : needed) {
0182         const uint64_t childNode = makeId(++m_uniqueIndex, fileIndex(need));
0183         m_parentMap.push_back(parent.internalId());
0184         m_childMap.push_back({});
0185         m_childMap[node].push_back(childNode);
0186         assert((uint32_t)m_parentMap.size() == m_uniqueIndex + 1);
0187         assert((uint32_t)m_childMap.size() == m_uniqueIndex + 1);
0188     }
0189 
0190     return m_childMap.at(node).size();
0191 }
0192 
0193 QModelIndex DependencyModel::parent(const QModelIndex& child) const
0194 {
0195     if (!m_fileSet || !child.isValid())
0196         return {};
0197 
0198     const auto node = nodeId(child.internalId());
0199     assert((uint32_t)m_parentMap.size() > node);
0200 
0201     const auto parentId = m_parentMap.at(node);
0202     const auto parentNode = nodeId(parentId);
0203     if (parentNode == 0)
0204         return {};
0205 
0206     assert((uint32_t)m_parentMap.size() > parentNode);
0207     const auto grandParentNode = nodeId(m_parentMap.at(parentNode));
0208 
0209     assert((uint32_t)m_childMap.size() > grandParentNode);
0210     const auto &children = m_childMap.at(grandParentNode);
0211     const auto row = children.indexOf(parentId);
0212     assert(row >= 0);
0213     return createIndex(row, 0, parentId);
0214 }
0215 
0216 QModelIndex DependencyModel::index(int row, int column, const QModelIndex& parent) const
0217 {
0218     if (!m_fileSet || !hasIndex(row, column, parent))
0219         return {};
0220 
0221     const auto node = nodeId(parent.internalId());
0222     assert(node < (uint32_t)m_childMap.size());
0223 
0224     const auto &children = m_childMap.at(node);
0225     if (row >= children.size())
0226         return {};
0227     assert(row < children.size());
0228 
0229     return createIndex(row, column, children.at(row));
0230 }
0231 
0232 QVariant DependencyModel::headerData(int section, Qt::Orientation orientation, int role) const
0233 {
0234     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0235         switch (section) {
0236             case 0: return tr("Library");
0237             case 1: return tr("Symbols Used");
0238         }
0239     }
0240     return QAbstractItemModel::headerData(section, orientation, role);
0241 }
0242 
0243 uint64_t DependencyModel::makeId(uint32_t id, int32_t fileIndex) const
0244 {
0245     uint64_t f = (uint32_t)fileIndex;
0246     return id | f << 32;
0247 }
0248 
0249 int32_t DependencyModel::fileIndex(uint64_t qmiId) const
0250 {
0251     return qmiId >> 32;
0252 }
0253 
0254 int32_t DependencyModel::fileIndex(const QByteArray& needed) const
0255 {
0256     const auto it = m_fileIndex.constFind(needed);
0257     if (it != m_fileIndex.cend())
0258         return it.value();
0259     return InvalidFile;
0260 }
0261 
0262 uint32_t DependencyModel::nodeId(uint64_t qmiId) const
0263 {
0264     return qmiId;
0265 }
0266 
0267 bool DependencyModel::hasCycle(const QModelIndex& index) const
0268 {
0269     const auto file = fileIndex(index.internalId());
0270 
0271     QModelIndex parentIndex(index);
0272     forever {
0273         parentIndex = parent(parentIndex);
0274         if (!parentIndex.isValid())
0275             return false;
0276         const auto parentFile = fileIndex(parentIndex.internalId());
0277         if (parentFile == file)
0278             return true;
0279     }
0280     Q_UNREACHABLE();
0281 }
0282 
0283 int DependencyModel::usedSymbolCount(int parentId, int fileId) const
0284 {
0285     assert(parentId != fileId);
0286     assert(parentId >= 0);
0287     assert(fileId >= 0);
0288     return DependenciesCheck::usedSymbolCount(m_fileSet->file(parentId), m_fileSet->file(fileId));
0289 }