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:
Nikolai Kosjar
2014-09-25 11:11:58 +02:00
committed by Eike Ziller
parent b3e07a53db
commit b9f9eb7ae5
42 changed files with 3290 additions and 0 deletions

View 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