1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Author: Frank Osterfeld, KDAB (frank.osterfeld@kdab.com)
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Creator.
8 **
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ****************************************************************************/
26 
27 #include "errorlistmodel.h"
28 #include "error.h"
29 #include "frame.h"
30 #include "stack.h"
31 #include "modelhelpers.h"
32 
33 #include <debugger/analyzer/diagnosticlocation.h>
34 #include <utils/qtcassert.h>
35 
36 #include <QCoreApplication>
37 #include <QDir>
38 #include <QVector>
39 
40 #include <cmath>
41 
42 namespace Valgrind {
43 namespace XmlProtocol {
44 
45 class ErrorItem : public Utils::TreeItem
46 {
47 public:
48     ErrorItem(const ErrorListModel *model, const Error &error);
49 
modelPrivate() const50     const ErrorListModel *modelPrivate() const { return m_model; }
error() const51     Error error() const { return m_error; }
52 
53 private:
54     QVariant data(int column, int role) const override;
55 
56     const ErrorListModel * const m_model;
57     const Error m_error;
58 };
59 
60 class StackItem : public Utils::TreeItem
61 {
62 public:
63     StackItem(const Stack &stack);
64 
65 private:
66     QVariant data(int column, int role) const override;
67 
68     const ErrorItem *getErrorItem() const;
69 
70     const Stack m_stack;
71 };
72 
73 class FrameItem : public Utils::TreeItem
74 {
75 public:
76     FrameItem(const Frame &frame);
77 
78 private:
79     QVariant data(int column, int role) const override;
80 
81     const ErrorItem *getErrorItem() const;
82 
83     const Frame m_frame;
84 };
85 
86 
ErrorListModel(QObject * parent)87 ErrorListModel::ErrorListModel(QObject *parent)
88     : Utils::TreeModel<>(parent)
89 {
90     setHeader(QStringList() << tr("Issue") << tr("Location"));
91 }
92 
findRelevantFrame(const Error & error) const93 Frame ErrorListModel::findRelevantFrame(const Error &error) const
94 {
95     if (m_relevantFrameFinder)
96         return m_relevantFrameFinder(error);
97     const QVector<Stack> stacks = error.stacks();
98     if (stacks.isEmpty())
99         return Frame();
100     const Stack &stack = stacks[0];
101     const QVector<Frame> frames = stack.frames();
102     if (!frames.isEmpty())
103         return frames.first();
104     return Frame();
105 }
106 
makeFrameName(const Frame & frame,bool withLocation)107 static QString makeFrameName(const Frame &frame, bool withLocation)
108 {
109     const QString d = frame.directory();
110     const QString f = frame.fileName();
111     const QString fn = frame.functionName();
112     const QString fullPath = frame.filePath();
113 
114     QString path;
115     if (!d.isEmpty() && !f.isEmpty())
116         path = fullPath;
117     else
118         path = frame.object();
119 
120     if (QFile::exists(path))
121         path = QFileInfo(path).canonicalFilePath();
122 
123     if (frame.line() != -1)
124         path += ':' + QString::number(frame.line());
125 
126     if (!fn.isEmpty()) {
127         const QString location = withLocation || path == frame.object()
128                 ? QString::fromLatin1(" in %2").arg(path) : QString();
129         return QCoreApplication::translate("Valgrind::Internal", "%1%2").arg(fn, location);
130     }
131     if (!path.isEmpty())
132         return path;
133     return QString::fromLatin1("0x%1").arg(frame.instructionPointer(), 0, 16);
134 }
135 
errorLocation(const Error & error) const136 QString ErrorListModel::errorLocation(const Error &error) const
137 {
138     return QCoreApplication::translate("Valgrind::Internal", "in %1").
139             arg(makeFrameName(findRelevantFrame(error), true));
140 }
141 
addError(const Error & error)142 void ErrorListModel::addError(const Error &error)
143 {
144     rootItem()->appendChild(new ErrorItem(this, error));
145 }
146 
relevantFrameFinder() const147 ErrorListModel::RelevantFrameFinder ErrorListModel::relevantFrameFinder() const
148 {
149     return m_relevantFrameFinder;
150 }
151 
setRelevantFrameFinder(const RelevantFrameFinder & relevantFrameFinder)152 void ErrorListModel::setRelevantFrameFinder(const RelevantFrameFinder &relevantFrameFinder)
153 {
154     m_relevantFrameFinder = relevantFrameFinder;
155 }
156 
157 
ErrorItem(const ErrorListModel * model,const Error & error)158 ErrorItem::ErrorItem(const ErrorListModel *model, const Error &error)
159     : m_model(model), m_error(error)
160 {
161     QTC_ASSERT(!m_error.stacks().isEmpty(), return);
162 
163     // If there's more than one stack, we simply map the real tree structure.
164     // Otherwise, we skip the stack level, which has no useful information and would
165     // just annoy the user.
166     // The same goes for the frame level.
167     if (m_error.stacks().count() > 1) {
168         foreach (const Stack &s, m_error.stacks())
169             appendChild(new StackItem(s));
170     } else if (m_error.stacks().constFirst().frames().count() > 1) {
171         foreach (const Frame &f, m_error.stacks().constFirst().frames())
172             appendChild(new FrameItem(f));
173     }
174 }
175 
locationData(int role,const Frame & frame)176 static QVariant locationData(int role, const Frame &frame)
177 {
178     const Debugger::DiagnosticLocation location(Utils::FilePath::fromString(frame.filePath()),
179                                                 frame.line(),
180                                                 0);
181     return Debugger::DetailedErrorView::locationData(role, location);
182 }
183 
data(int column,int role) const184 QVariant ErrorItem::data(int column, int role) const
185 {
186     if (column == Debugger::DetailedErrorView::LocationColumn)
187         return locationData(role, m_model->findRelevantFrame(m_error));
188 
189     // DiagnosticColumn
190     switch (role) {
191     case Debugger::DetailedErrorView::FullTextRole: {
192         QString content;
193         QTextStream stream(&content);
194 
195         stream << m_error.what() << "\n";
196         stream << "  "
197                << m_model->errorLocation(m_error)
198                << "\n";
199 
200         foreach (const Stack &stack, m_error.stacks()) {
201             if (!stack.auxWhat().isEmpty())
202                 stream << stack.auxWhat();
203             int i = 1;
204             foreach (const Frame &frame, stack.frames())
205                 stream << "  " << i++ << ": " << makeFrameName(frame, true) << "\n";
206         }
207 
208         stream.flush();
209         return content;
210     }
211     case ErrorListModel::ErrorRole:
212         return QVariant::fromValue<Error>(m_error);
213     case Qt::DisplayRole:
214         // If and only if there is exactly one frame, we have omitted creating a child item for it
215         // (see the constructor) and display the function name in the error item instead.
216         if (m_error.stacks().count() != 1 || m_error.stacks().constFirst().frames().count() != 1
217                 || m_error.stacks().constFirst().frames().constFirst().functionName().isEmpty()) {
218             return m_error.what();
219         }
220         return ErrorListModel::tr("%1 in function %2")
221                 .arg(m_error.what(), m_error.stacks().constFirst().frames().constFirst().functionName());
222     case Qt::ToolTipRole:
223         return toolTipForFrame(m_model->findRelevantFrame(m_error));
224     default:
225         return QVariant();
226     }
227 }
228 
229 
StackItem(const Stack & stack)230 StackItem::StackItem(const Stack &stack) : m_stack(stack)
231 {
232     foreach (const Frame &f, m_stack.frames())
233         appendChild(new FrameItem(f));
234 }
235 
data(int column,int role) const236 QVariant StackItem::data(int column, int role) const
237 {
238     const ErrorItem * const errorItem = getErrorItem();
239     if (column == Debugger::DetailedErrorView::LocationColumn)
240         return locationData(role, errorItem->modelPrivate()->findRelevantFrame(errorItem->error()));
241 
242     // DiagnosticColumn
243     switch (role) {
244     case ErrorListModel::ErrorRole:
245         return QVariant::fromValue(errorItem->error());
246     case Qt::DisplayRole:
247         return m_stack.auxWhat().isEmpty() ? errorItem->error().what() : m_stack.auxWhat();
248     case Qt::ToolTipRole:
249         return toolTipForFrame(errorItem->modelPrivate()->findRelevantFrame(errorItem->error()));
250     default:
251         return QVariant();
252     }
253 }
254 
getErrorItem() const255 const ErrorItem *StackItem::getErrorItem() const
256 {
257     return static_cast<ErrorItem *>(parent());
258 }
259 
260 
FrameItem(const Frame & frame)261 FrameItem::FrameItem(const Frame &frame) : m_frame(frame)
262 {
263 }
264 
data(int column,int role) const265 QVariant FrameItem::data(int column, int role) const
266 {
267     if (column == Debugger::DetailedErrorView::LocationColumn)
268         return locationData(role, m_frame);
269 
270     // DiagnosticColumn
271     switch (role) {
272     case ErrorListModel::ErrorRole:
273         return QVariant::fromValue(getErrorItem()->error());
274     case Qt::DisplayRole: {
275         const int row = indexInParent() + 1;
276         const int padding = static_cast<int>(std::log10(parent()->childCount()))
277                 - static_cast<int>(std::log10(row));
278         return QString::fromLatin1("%1%2: %3")
279                 .arg(QString(padding, ' '))
280                 .arg(row)
281                 .arg(makeFrameName(m_frame, false));
282     }
283     case Qt::ToolTipRole:
284         return toolTipForFrame(m_frame);
285     default:
286         return QVariant();
287     }
288 }
289 
getErrorItem() const290 const ErrorItem *FrameItem::getErrorItem() const
291 {
292     for (const TreeItem *parentItem = parent(); parentItem; parentItem = parentItem->parent()) {
293         auto const errorItem = dynamic_cast<const ErrorItem *>(parentItem);
294         if (errorItem)
295             return errorItem;
296     }
297     QTC_CHECK(false);
298     return nullptr;
299 }
300 
301 } // namespace XmlProtocol
302 } // namespace Valgrind
303