2014-09-25 11:11:58 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** This file is part of Qt Creator.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
2014-09-25 11:11:58 +02:00
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-14 10:59:10 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "clangstaticanalyzerlogfilereader.h"
|
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QObject>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QXmlStreamReader>
|
|
|
|
|
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
|
2018-03-14 12:58:12 +01:00
|
|
|
namespace ClangTools {
|
2014-09-25 11:11:58 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
class ClangStaticAnalyzerLogFileReader
|
|
|
|
|
{
|
2018-03-14 12:58:12 +01:00
|
|
|
Q_DECLARE_TR_FUNCTIONS(ClangTools::Internal::ClangStaticAnalyzerLogFileReader)
|
2014-09-25 11:11:58 +02:00
|
|
|
public:
|
|
|
|
|
ClangStaticAnalyzerLogFileReader(const QString &filePath);
|
|
|
|
|
|
|
|
|
|
QXmlStreamReader::Error read();
|
|
|
|
|
|
|
|
|
|
// Output
|
|
|
|
|
QString clangVersion() const;
|
|
|
|
|
QStringList files() const;
|
|
|
|
|
QList<Diagnostic> diagnostics() const;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void readPlist();
|
|
|
|
|
void readTopLevelDict();
|
|
|
|
|
void readDiagnosticsArray();
|
|
|
|
|
void readDiagnosticsDict();
|
|
|
|
|
QList<ExplainingStep> readPathArray();
|
|
|
|
|
ExplainingStep readPathDict();
|
2016-03-02 13:57:37 +01:00
|
|
|
Debugger::DiagnosticLocation readLocationDict(bool elementIsRead = false);
|
|
|
|
|
QList<Debugger::DiagnosticLocation> readRangesArray();
|
2014-09-25 11:11:58 +02:00
|
|
|
|
|
|
|
|
QString readString();
|
|
|
|
|
QStringList readStringArray();
|
|
|
|
|
int readInteger(bool *convertedSuccessfully);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_filePath;
|
|
|
|
|
QXmlStreamReader m_xml;
|
|
|
|
|
|
|
|
|
|
QString m_clangVersion;
|
|
|
|
|
QStringList m_referencedFiles;
|
|
|
|
|
QList<Diagnostic> m_diagnostics;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QList<Diagnostic> LogFileReader::read(const QString &filePath, QString *errorMessage)
|
|
|
|
|
{
|
|
|
|
|
const QList<Diagnostic> emptyList;
|
|
|
|
|
|
|
|
|
|
// Check file path
|
|
|
|
|
QFileInfo fi(filePath);
|
|
|
|
|
if (!fi.exists() || !fi.isReadable()) {
|
|
|
|
|
if (errorMessage) {
|
2016-04-26 16:47:04 +09:00
|
|
|
*errorMessage = tr("File \"%1\" does not exist or is not readable.")
|
2014-09-25 11:11:58 +02:00
|
|
|
.arg(filePath);
|
|
|
|
|
}
|
|
|
|
|
return emptyList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read
|
|
|
|
|
ClangStaticAnalyzerLogFileReader reader(filePath);
|
|
|
|
|
const QXmlStreamReader::Error error = reader.read();
|
|
|
|
|
|
|
|
|
|
// Return diagnostics
|
|
|
|
|
switch (error) {
|
|
|
|
|
case QXmlStreamReader::NoError:
|
|
|
|
|
return reader.diagnostics();
|
|
|
|
|
|
|
|
|
|
// Handle errors
|
|
|
|
|
case QXmlStreamReader::UnexpectedElementError:
|
|
|
|
|
if (errorMessage) {
|
2016-04-26 16:47:04 +09:00
|
|
|
*errorMessage = tr("Could not read file \"%1\": UnexpectedElementError.")
|
2014-09-25 11:11:58 +02:00
|
|
|
.arg(filePath);
|
2018-01-10 16:51:58 +01:00
|
|
|
}
|
|
|
|
|
Q_FALLTHROUGH();
|
2014-09-25 11:11:58 +02:00
|
|
|
case QXmlStreamReader::CustomError:
|
|
|
|
|
if (errorMessage) {
|
2016-04-26 16:47:04 +09:00
|
|
|
*errorMessage = tr("Could not read file \"%1\": CustomError.")
|
2014-09-25 11:11:58 +02:00
|
|
|
.arg(filePath);
|
2018-01-10 16:51:58 +01:00
|
|
|
}
|
|
|
|
|
Q_FALLTHROUGH();
|
2014-09-25 11:11:58 +02:00
|
|
|
case QXmlStreamReader::NotWellFormedError:
|
|
|
|
|
if (errorMessage) {
|
2016-04-26 16:47:04 +09:00
|
|
|
*errorMessage = tr("Could not read file \"%1\": NotWellFormedError.")
|
2014-09-25 11:11:58 +02:00
|
|
|
.arg(filePath);
|
2018-01-10 16:51:58 +01:00
|
|
|
}
|
|
|
|
|
Q_FALLTHROUGH();
|
2014-09-25 11:11:58 +02:00
|
|
|
case QXmlStreamReader::PrematureEndOfDocumentError:
|
|
|
|
|
if (errorMessage) {
|
2016-04-26 16:47:04 +09:00
|
|
|
*errorMessage = tr("Could not read file \"%1\": PrematureEndOfDocumentError.")
|
2014-09-25 11:11:58 +02:00
|
|
|
.arg(filePath);
|
2018-01-10 16:51:58 +01:00
|
|
|
}
|
|
|
|
|
Q_FALLTHROUGH();
|
2014-09-25 11:11:58 +02:00
|
|
|
default:
|
|
|
|
|
return emptyList;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClangStaticAnalyzerLogFileReader::ClangStaticAnalyzerLogFileReader(const QString &filePath)
|
|
|
|
|
: m_filePath(filePath)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QXmlStreamReader::Error ClangStaticAnalyzerLogFileReader::read()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!m_filePath.isEmpty(), return QXmlStreamReader::CustomError);
|
|
|
|
|
QFile file(m_filePath);
|
|
|
|
|
QTC_ASSERT(file.open(QIODevice::ReadOnly | QIODevice::Text),
|
|
|
|
|
return QXmlStreamReader::CustomError);
|
|
|
|
|
|
|
|
|
|
m_xml.setDevice(&file);
|
|
|
|
|
readPlist();
|
|
|
|
|
|
|
|
|
|
// If file is empty, m_xml.error() == QXmlStreamReader::PrematureEndOfDocumentError
|
|
|
|
|
return m_xml.error();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ClangStaticAnalyzerLogFileReader::clangVersion() const
|
|
|
|
|
{
|
|
|
|
|
return m_clangVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList ClangStaticAnalyzerLogFileReader::files() const
|
|
|
|
|
{
|
|
|
|
|
return m_referencedFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<Diagnostic> ClangStaticAnalyzerLogFileReader::diagnostics() const
|
|
|
|
|
{
|
|
|
|
|
return m_diagnostics;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangStaticAnalyzerLogFileReader::readPlist()
|
|
|
|
|
{
|
|
|
|
|
if (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("plist")) {
|
|
|
|
|
if (m_xml.attributes().value(QLatin1String("version")) == QLatin1String("1.0"))
|
|
|
|
|
readTopLevelDict();
|
|
|
|
|
} else {
|
2016-04-26 16:47:04 +09:00
|
|
|
m_xml.raiseError(tr("File is not a plist version 1.0 file."));
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangStaticAnalyzerLogFileReader::readTopLevelDict()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("plist"), return);
|
|
|
|
|
QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"), return);
|
|
|
|
|
|
|
|
|
|
while (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("key")) {
|
|
|
|
|
const QString key = m_xml.readElementText();
|
|
|
|
|
if (key == QLatin1String("clang_version"))
|
|
|
|
|
m_clangVersion = readString();
|
|
|
|
|
else if (key == QLatin1String("files"))
|
|
|
|
|
m_referencedFiles = readStringArray();
|
|
|
|
|
else if (key == QLatin1String("diagnostics"))
|
|
|
|
|
readDiagnosticsArray();
|
|
|
|
|
} else {
|
|
|
|
|
m_xml.skipCurrentElement();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangStaticAnalyzerLogFileReader::readDiagnosticsArray()
|
|
|
|
|
{
|
|
|
|
|
if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) {
|
|
|
|
|
while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"))
|
|
|
|
|
readDiagnosticsDict();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangStaticAnalyzerLogFileReader::readDiagnosticsDict()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"), return);
|
|
|
|
|
|
|
|
|
|
Diagnostic diagnostic;
|
|
|
|
|
|
|
|
|
|
while (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("key")) {
|
|
|
|
|
const QString key = m_xml.readElementText();
|
|
|
|
|
if (key == QLatin1String("path"))
|
|
|
|
|
diagnostic.explainingSteps = readPathArray();
|
|
|
|
|
else if (key == QLatin1String("description"))
|
|
|
|
|
diagnostic.description = readString();
|
|
|
|
|
else if (key == QLatin1String("category"))
|
|
|
|
|
diagnostic.category = readString();
|
|
|
|
|
else if (key == QLatin1String("type"))
|
|
|
|
|
diagnostic.type = readString();
|
|
|
|
|
else if (key == QLatin1String("issue_context_kind"))
|
|
|
|
|
diagnostic.issueContextKind = readString();
|
|
|
|
|
else if (key == QLatin1String("issue_context"))
|
|
|
|
|
diagnostic.issueContext = readString();
|
|
|
|
|
else if (key == QLatin1String("location"))
|
|
|
|
|
diagnostic.location = readLocationDict();
|
|
|
|
|
} else {
|
|
|
|
|
m_xml.skipCurrentElement();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_diagnostics << diagnostic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<ExplainingStep> ClangStaticAnalyzerLogFileReader::readPathArray()
|
|
|
|
|
{
|
|
|
|
|
QList<ExplainingStep> result;
|
|
|
|
|
|
|
|
|
|
if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) {
|
|
|
|
|
while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict")) {
|
|
|
|
|
const ExplainingStep step = readPathDict();
|
|
|
|
|
if (step.isValid())
|
|
|
|
|
result << step;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExplainingStep ClangStaticAnalyzerLogFileReader::readPathDict()
|
|
|
|
|
{
|
|
|
|
|
ExplainingStep explainingStep;
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"),
|
|
|
|
|
return explainingStep);
|
|
|
|
|
|
|
|
|
|
// We are interested only in dict entries an kind=event type
|
|
|
|
|
if (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("key")) {
|
|
|
|
|
const QString key = m_xml.readElementText();
|
|
|
|
|
QTC_ASSERT(key == QLatin1String("kind"), return explainingStep);
|
|
|
|
|
const QString kind = readString();
|
|
|
|
|
if (kind != QLatin1String("event")) {
|
|
|
|
|
m_xml.skipCurrentElement();
|
|
|
|
|
return explainingStep;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-18 16:23:34 +01:00
|
|
|
bool depthOk = false;
|
2014-09-25 11:11:58 +02:00
|
|
|
|
|
|
|
|
while (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("key")) {
|
|
|
|
|
const QString key = m_xml.readElementText();
|
|
|
|
|
if (key == QLatin1String("location"))
|
|
|
|
|
explainingStep.location = readLocationDict();
|
|
|
|
|
else if (key == QLatin1String("ranges"))
|
|
|
|
|
explainingStep.ranges = readRangesArray();
|
|
|
|
|
else if (key == QLatin1String("depth"))
|
|
|
|
|
explainingStep.depth = readInteger(&depthOk);
|
|
|
|
|
else if (key == QLatin1String("message"))
|
|
|
|
|
explainingStep.message = readString();
|
|
|
|
|
else if (key == QLatin1String("extended_message"))
|
|
|
|
|
explainingStep.extendedMessage = readString();
|
|
|
|
|
} else {
|
|
|
|
|
m_xml.skipCurrentElement();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTC_CHECK(depthOk);
|
|
|
|
|
return explainingStep;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 13:57:37 +01:00
|
|
|
Debugger::DiagnosticLocation ClangStaticAnalyzerLogFileReader::readLocationDict(bool elementIsRead)
|
2014-09-25 11:11:58 +02:00
|
|
|
{
|
2016-03-02 13:57:37 +01:00
|
|
|
Debugger::DiagnosticLocation location;
|
2014-09-25 11:11:58 +02:00
|
|
|
if (elementIsRead) {
|
|
|
|
|
QTC_ASSERT(m_xml.isStartElement() && m_xml.name() == QLatin1String("dict"),
|
|
|
|
|
return location);
|
|
|
|
|
} else {
|
|
|
|
|
QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"),
|
|
|
|
|
return location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int line = 0;
|
|
|
|
|
int column = 0;
|
|
|
|
|
int fileIndex = 0;
|
2014-11-18 16:23:34 +01:00
|
|
|
bool lineOk = false, columnOk = false, fileIndexOk = false;
|
2014-09-25 11:11:58 +02:00
|
|
|
|
|
|
|
|
// Collect values
|
|
|
|
|
while (m_xml.readNextStartElement()) {
|
|
|
|
|
if (m_xml.name() == QLatin1String("key")) {
|
|
|
|
|
const QString keyName = m_xml.readElementText();
|
|
|
|
|
if (keyName == QLatin1String("line"))
|
|
|
|
|
line = readInteger(&lineOk);
|
|
|
|
|
else if (keyName == QLatin1String("col"))
|
|
|
|
|
column = readInteger(&columnOk);
|
|
|
|
|
else if (keyName == QLatin1String("file"))
|
|
|
|
|
fileIndex = readInteger(&fileIndexOk);
|
|
|
|
|
} else {
|
|
|
|
|
m_xml.skipCurrentElement();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lineOk && columnOk && fileIndexOk) {
|
|
|
|
|
QTC_ASSERT(fileIndex < m_referencedFiles.size(), return location);
|
2016-03-02 13:57:37 +01:00
|
|
|
location = Debugger::DiagnosticLocation(m_referencedFiles.at(fileIndex), line, column);
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
return location;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 13:57:37 +01:00
|
|
|
QList<Debugger::DiagnosticLocation> ClangStaticAnalyzerLogFileReader::readRangesArray()
|
2014-09-25 11:11:58 +02:00
|
|
|
{
|
2016-03-02 13:57:37 +01:00
|
|
|
QList<Debugger::DiagnosticLocation> result;
|
2014-09-25 11:11:58 +02:00
|
|
|
|
|
|
|
|
// It's an array of arrays...
|
|
|
|
|
QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array"),
|
|
|
|
|
return result);
|
|
|
|
|
QTC_ASSERT(m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array"),
|
|
|
|
|
return result);
|
|
|
|
|
|
|
|
|
|
while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("dict"))
|
|
|
|
|
result << readLocationDict(true);
|
|
|
|
|
|
|
|
|
|
m_xml.skipCurrentElement(); // Laeve outer array
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ClangStaticAnalyzerLogFileReader::readString()
|
|
|
|
|
{
|
|
|
|
|
if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("string"))
|
|
|
|
|
return m_xml.readElementText();
|
|
|
|
|
|
2016-04-26 16:47:04 +09:00
|
|
|
m_xml.raiseError(tr("Expected a string element."));
|
2014-09-25 11:11:58 +02:00
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList ClangStaticAnalyzerLogFileReader::readStringArray()
|
|
|
|
|
{
|
|
|
|
|
if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("array")) {
|
|
|
|
|
QStringList result;
|
|
|
|
|
while (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("string")) {
|
|
|
|
|
const QString string = m_xml.readElementText();
|
|
|
|
|
if (!string.isEmpty())
|
|
|
|
|
result << string;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 16:47:04 +09:00
|
|
|
m_xml.raiseError(tr("Expected an array element."));
|
2014-09-25 11:11:58 +02:00
|
|
|
return QStringList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ClangStaticAnalyzerLogFileReader::readInteger(bool *convertedSuccessfully)
|
|
|
|
|
{
|
|
|
|
|
if (m_xml.readNextStartElement() && m_xml.name() == QLatin1String("integer")) {
|
|
|
|
|
const QString contents = m_xml.readElementText();
|
|
|
|
|
return contents.toInt(convertedSuccessfully);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 16:47:04 +09:00
|
|
|
m_xml.raiseError(tr("Expected an integer element."));
|
2014-09-25 11:11:58 +02:00
|
|
|
if (convertedSuccessfully)
|
|
|
|
|
*convertedSuccessfully = false;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
2018-03-14 12:58:12 +01:00
|
|
|
} // namespace ClangTools
|