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