forked from qt-creator/qt-creator
Import Clang Static Analyzer plugin
This plugin adds "Clang Static Analyzer" to the Analyze mode, which processes all implementation/source project files of the current project. For this, it will call the clang executable for each file. The found diagnostics will be displayed in a view similar to the one used in "Valgrind Memory Analyzer". The user can specify the clang executable to use and the number of concurrent processes to launch in Menu: Tools > Options > Analyzer > Clang Static Analyzer. Main TODOs: * Fiddle around the appropriate command line options, currently only defines and include paths are passed on. * Tests on Windows / OS X. * Remove dependency to clangcodemodel by moving the functions that create command line arguments to CppTools. Mostly they are not even specific to clang (but would also work with gcc). * Maybe limit to a range of tested clang versions. * How to deal with directory containing all the log files after the user starts a new run or Creator is shut down? (delete it? leave it there? make it configurable?). * Find out how to properly integrate the tests. Imaginable future additions: * Adding a button to load result/log files from a directory, e.g. if the user used the 'scan-build' approach. * Adding a button with a filter menu in order to display only diagnostics from certain categories, similar to "Valgrind Memory Analyzer". Change-Id: I6aeb5dfdbdfa239a06c03dd8759a983df71b77ea Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
This commit is contained in:
committed by
Eike Ziller
parent
b3e07a53db
commit
b9f9eb7ae5
374
plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.cpp
Normal file
374
plugins/clangstaticanalyzer/clangstaticanalyzerlogfilereader.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc
|
||||
** All rights reserved.
|
||||
** For any questions to Digia, please use contact form at http://qt.digia.com <http://qt.digia.com/>
|
||||
**
|
||||
** This file is part of the Qt Enterprise LicenseChecker Add-on.
|
||||
**
|
||||
** Licensees holding valid Qt Enterprise licenses may use this file in
|
||||
** accordance with the Qt Enterprise License Agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please use
|
||||
** contact form at http://qt.digia.com <http://qt.digia.com/>
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "clangstaticanalyzerlogfilereader.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
namespace ClangStaticAnalyzer {
|
||||
namespace Internal {
|
||||
|
||||
class ClangStaticAnalyzerLogFileReader
|
||||
{
|
||||
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();
|
||||
Location readLocationDict(bool elementIsRead = false);
|
||||
QList<Location> readRangesArray();
|
||||
|
||||
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) {
|
||||
*errorMessage = QObject::tr("File \"%1\" does not exist or is not readable.")
|
||||
.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) {
|
||||
*errorMessage = QObject::tr("Could not read file \"%1\": UnexpectedElementError.")
|
||||
.arg(filePath);
|
||||
} // fall-through
|
||||
case QXmlStreamReader::CustomError:
|
||||
if (errorMessage) {
|
||||
*errorMessage = QObject::tr("Could not read file \"%1\": CustomError.")
|
||||
.arg(filePath);
|
||||
} // fall-through
|
||||
case QXmlStreamReader::NotWellFormedError:
|
||||
if (errorMessage) {
|
||||
*errorMessage = QObject::tr("Could not read file \"%1\": NotWellFormedError.")
|
||||
.arg(filePath);
|
||||
} // fall-through
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
if (errorMessage) {
|
||||
*errorMessage = QObject::tr("Could not read file \"%1\": PrematureEndOfDocumentError.")
|
||||
.arg(filePath);
|
||||
} // fall-through
|
||||
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 {
|
||||
m_xml.raiseError(QObject::tr("File is not a plist version 1.0 file."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool depthOk;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Location ClangStaticAnalyzerLogFileReader::readLocationDict(bool elementIsRead)
|
||||
{
|
||||
Location location;
|
||||
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;
|
||||
bool lineOk, columnOk, fileIndexOk;
|
||||
|
||||
// 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);
|
||||
location = Location(m_referencedFiles.at(fileIndex), line, column);
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
QList<Location> ClangStaticAnalyzerLogFileReader::readRangesArray()
|
||||
{
|
||||
QList<Location> result;
|
||||
|
||||
// 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();
|
||||
|
||||
m_xml.raiseError(QObject::tr("Expected a string element."));
|
||||
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;
|
||||
}
|
||||
|
||||
m_xml.raiseError(QObject::tr("Expected an array element."));
|
||||
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);
|
||||
}
|
||||
|
||||
m_xml.raiseError(QObject::tr("Expected an integer element."));
|
||||
if (convertedSuccessfully)
|
||||
*convertedSuccessfully = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangStaticAnalyzer
|
||||
Reference in New Issue
Block a user