Newer
Older
#include "cpppreprocessor.h"
#include <coreplugin/editormanager/editormanager.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/textfileformat.h>
#include <QCoreApplication>
#include <QCryptographicHash>
/*!
* \class CppTools::Internal::CppPreprocessor
* \brief The CppPreprocessor class updates set of indexed C++ files.
*
* Indexed file is truncated version of fully parsed document: copy of source
* code and full AST will be dropped when indexing is done. Working copy ensures
* that documents with most recent copy placed in memory will be parsed correctly.
*
* \sa CPlusPlus::Document
* \sa CppTools::CppModelManagerInterface::WorkingCopy
*/
using namespace CPlusPlus;
using namespace CppTools;
using namespace CppTools::Internal;
CppPreprocessor::CppPreprocessor(QPointer<CppModelManager> modelManager,
bool dumpFileNameWhileParsing)
: m_snapshot(modelManager->snapshot()),
m_modelManager(modelManager),
m_dumpFileNameWhileParsing(dumpFileNameWhileParsing),
m_preprocess(this, &m_env),
m_revision(0),
m_defaultCodec(Core::EditorManager::defaultTextCodec())
{
m_preprocess.setKeepComments(true);
}
CppPreprocessor::CppPreprocessor(QPointer<CppModelManager> modelManager, const Snapshot &snapshot,
bool dumpFileNameWhileParsing)
: m_snapshot(snapshot),
m_modelManager(modelManager),
m_dumpFileNameWhileParsing(dumpFileNameWhileParsing),
m_preprocess(this, &m_env),
m_revision(0),
m_defaultCodec(Core::EditorManager::defaultTextCodec())
{
m_preprocess.setKeepComments(true);
}
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
CppPreprocessor::~CppPreprocessor()
{ }
void CppPreprocessor::setRevision(unsigned revision)
{ m_revision = revision; }
void CppPreprocessor::setWorkingCopy(const CppModelManagerInterface::WorkingCopy &workingCopy)
{ m_workingCopy = workingCopy; }
void CppPreprocessor::setIncludePaths(const QStringList &includePaths)
{
m_includePaths.clear();
for (int i = 0; i < includePaths.size(); ++i) {
const QString &path = includePaths.at(i);
if (Utils::HostOsInfo::isMacHost()) {
if (i + 1 < includePaths.size() && path.endsWith(QLatin1String(".framework/Headers"))) {
const QFileInfo pathInfo(path);
const QFileInfo frameworkFileInfo(pathInfo.path());
const QString frameworkName = frameworkFileInfo.baseName();
const QFileInfo nextIncludePath = includePaths.at(i + 1);
if (nextIncludePath.fileName() == frameworkName) {
// We got a QtXXX.framework/Headers followed by $QTDIR/include/QtXXX.
// In this case we prefer to include files from $QTDIR/include/QtXXX.
continue;
}
}
}
m_includePaths.append(cleanPath(path));
}
}
void CppPreprocessor::setFrameworkPaths(const QStringList &frameworkPaths)
{
m_frameworkPaths.clear();
foreach (const QString &frameworkPath, frameworkPaths)
addFrameworkPath(frameworkPath);
}
// Add the given framework path, and expand private frameworks.
//
// Example:
// <framework-path>/ApplicationServices.framework
// has private frameworks in:
// <framework-path>/ApplicationServices.framework/Frameworks
// if the "Frameworks" folder exists inside the top level framework.
void CppPreprocessor::addFrameworkPath(const QString &frameworkPath)
{
// The algorithm below is a bit too eager, but that's because we're not getting
// in the frameworks we're linking against. If we would have that, then we could
// add only those private frameworks.
const QString cleanFrameworkPath = cleanPath(frameworkPath);
if (!m_frameworkPaths.contains(cleanFrameworkPath))
m_frameworkPaths.append(cleanFrameworkPath);
const QDir frameworkDir(cleanFrameworkPath);
const QStringList filter = QStringList() << QLatin1String("*.framework");
foreach (const QFileInfo &framework, frameworkDir.entryInfoList(filter)) {
if (!framework.isDir())
continue;
const QFileInfo privateFrameworks(framework.absoluteFilePath(),
QLatin1String("Frameworks"));
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
if (privateFrameworks.exists() && privateFrameworks.isDir())
addFrameworkPath(privateFrameworks.absoluteFilePath());
}
}
void CppPreprocessor::setTodo(const QStringList &files)
{ m_todo = QSet<QString>::fromList(files); }
namespace {
class Process: public std::unary_function<Document::Ptr, void>
{
QPointer<CppModelManager> _modelManager;
Document::Ptr _doc;
Document::CheckMode _mode;
public:
Process(QPointer<CppModelManager> modelManager,
Document::Ptr doc,
const CppModelManager::WorkingCopy &workingCopy)
: _modelManager(modelManager),
_doc(doc),
_mode(Document::FastCheck)
{
if (workingCopy.contains(_doc->fileName()))
_mode = Document::FullCheck;
}
void operator()()
{
_doc->check(_mode);
if (_modelManager) {
_modelManager->emitDocumentUpdated(_doc);
_doc->releaseSourceAndAST();
}
}
};
} // end of anonymous namespace
void CppPreprocessor::run(const QString &fileName)
{
sourceNeeded(0, fileName, IncludeGlobal);
}
void CppPreprocessor::removeFromCache(const QString &fileName)
{
m_snapshot.remove(fileName);
}
void CppPreprocessor::resetEnvironment()
{
m_env.reset();
m_processed.clear();
}
void CppPreprocessor::getFileContents(const QString &absoluteFilePath,
QByteArray *contents,
unsigned *revision) const
{
if (absoluteFilePath.isEmpty())
return;
if (m_workingCopy.contains(absoluteFilePath)) {
const QPair<QByteArray, unsigned> entry = m_workingCopy.get(absoluteFilePath);
if (contents)
*contents = entry.first;
if (revision)
*revision = entry.second;
return;
}
if (contents) {
QString error;
if (Utils::TextFileFormat::readFileUTF8(absoluteFilePath, m_defaultCodec, contents, &error)
!= Utils::TextFileFormat::ReadSuccess) {
qWarning("Error reading file \"%s\": \"%s\".", qPrintable(absoluteFilePath),
qPrintable(error));
}
}
if (revision)
*revision = 0;
}
bool CppPreprocessor::checkFile(const QString &absoluteFilePath) const
{
if (absoluteFilePath.isEmpty() || m_included.contains(absoluteFilePath))
return true;
const QFileInfo fileInfo(absoluteFilePath);
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
return fileInfo.isFile() && fileInfo.isReadable();
}
/// Resolve the given file name to its absolute path w.r.t. the include type.
QString CppPreprocessor::resolveFile(const QString &fileName, IncludeType type)
{
if (type == IncludeGlobal) {
QHash<QString, QString>::ConstIterator it = m_fileNameCache.find(fileName);
if (it != m_fileNameCache.end())
return it.value();
const QString fn = resolveFile_helper(fileName, type);
m_fileNameCache.insert(fileName, fn);
return fn;
}
// IncludeLocal, IncludeNext
return resolveFile_helper(fileName, type);
}
QString CppPreprocessor::cleanPath(const QString &path)
{
QString result = QDir::cleanPath(path);
const QChar slash(QLatin1Char('/'));
if (!result.endsWith(slash))
result.append(slash);
return result;
}
QString CppPreprocessor::resolveFile_helper(const QString &fileName, IncludeType type)
{
if (isInjectedFile(fileName) || fileInfo.isAbsolute())
return fileName;
if (type == IncludeLocal && m_currentDoc) {
const QFileInfo currentFileInfo(m_currentDoc->fileName());
const QString path = cleanPath(currentFileInfo.absolutePath()) + fileName;
if (checkFile(path))
return path;
// Fall through! "16.2 Source file inclusion" from the standard states to continue
// searching as if this would be a global include.
}
foreach (const QString &includePath, m_includePaths) {
const QString path = includePath + fileName;
if (m_workingCopy.contains(path) || checkFile(path))
return path;
}
const int index = fileName.indexOf(QLatin1Char('/'));
const QString frameworkName = fileName.left(index);
const QString name = frameworkName + QLatin1String(".framework/Headers/")
+ fileName.mid(index + 1);
foreach (const QString &frameworkPath, m_frameworkPaths) {
const QString path = frameworkPath + name;
if (checkFile(path))
return path;
}
}
return QString();
}
void CppPreprocessor::macroAdded(const Macro ¯o)
{
return;
m_currentDoc->appendMacro(macro);
}
static inline const Macro revision(const CppModelManagerInterface::WorkingCopy &s,
const Macro ¯o)
{
Macro newMacro(macro);
newMacro.setFileRevision(s.get(macro.fileName()).second);
return newMacro;
}
void CppPreprocessor::passedMacroDefinitionCheck(unsigned offset, unsigned line, const Macro ¯o)
{
return;
m_currentDoc->addMacroUse(revision(m_workingCopy, macro), offset, macro.name().length(), line,
QVector<MacroArgumentReference>());
}
void CppPreprocessor::failedMacroDefinitionCheck(unsigned offset, const ByteArrayRef &name)
{
return;
m_currentDoc->addUndefinedMacroUse(QByteArray(name.start(), name.size()), offset);
}
void CppPreprocessor::notifyMacroReference(unsigned offset, unsigned line, const Macro ¯o)
{
return;
m_currentDoc->addMacroUse(revision(m_workingCopy, macro), offset, macro.name().length(), line,
QVector<MacroArgumentReference>());
}
void CppPreprocessor::startExpandingMacro(unsigned offset, unsigned line,
const Macro ¯o,
const QVector<MacroArgumentReference> &actuals)
{
m_currentDoc->addMacroUse(revision(m_workingCopy, macro), offset, macro.name().length(), line,
actuals);
}
void CppPreprocessor::stopExpandingMacro(unsigned, const Macro &)
{
return;
}
void CppPreprocessor::markAsIncludeGuard(const QByteArray ¯oName)
{
if (!m_currentDoc)
return;
m_currentDoc->setIncludeGuardMacroName(macroName);
}
void CppPreprocessor::mergeEnvironment(Document::Ptr doc)
{
return;
const QString fn = doc->fileName();
if (m_processed.contains(fn))
return;
m_processed.insert(fn);
foreach (const Document::Include &incl, doc->resolvedIncludes()) {
const QString includedFile = incl.resolvedFileName();
if (Document::Ptr includedDoc = m_snapshot.document(includedFile))
mergeEnvironment(includedDoc);
else
run(includedFile);
}
m_env.addMacros(doc->definedMacros());
}
void CppPreprocessor::startSkippingBlocks(unsigned offset)
{
if (m_currentDoc)
m_currentDoc->startSkippingBlocks(offset);
}
void CppPreprocessor::stopSkippingBlocks(unsigned offset)
{
if (m_currentDoc)
m_currentDoc->stopSkippingBlocks(offset);
}
// This is a temporary fix to handle non-ascii characters. This can be removed when the lexer can
// handle multi-byte characters.
static QByteArray convertToLatin1(const QByteArray &contents)
{
const char *p = contents.constData();
while (char ch = *p++)
if (ch & 0x80)
return QString::fromUtf8(contents).toLatin1();
return contents;
}
void CppPreprocessor::sourceNeeded(unsigned line, const QString &fileName, IncludeType type)
{
if (fileName.isEmpty())
return;
QString absoluteFileName = resolveFile(fileName, type);
absoluteFileName = QDir::cleanPath(absoluteFileName);
if (m_currentDoc)
m_currentDoc->addIncludeFile(Document::Include(fileName, absoluteFileName, line, type));
if (m_included.contains(absoluteFileName))
return; // we've already seen this file.
if (absoluteFileName != modelManager()->configurationFileName())
m_included.insert(absoluteFileName);
unsigned editorRevision = 0;
QByteArray contents;
getFileContents(absoluteFileName, &contents, &editorRevision);
contents = convertToLatin1(contents);
if (contents.isEmpty() && !QFileInfo(absoluteFileName).isAbsolute()) {
QString msg = QCoreApplication::translate(
"CppPreprocessor", "%1: No such file or directory").arg(fileName);
Document::DiagnosticMessage d(Document::DiagnosticMessage::Warning,
m_currentDoc->fileName(),
line, /*column = */ 0,
msg);
m_currentDoc->addDiagnosticMessage(d);
return;
}
}
if (m_dumpFileNameWhileParsing) {
qDebug() << "Parsing file:" << absoluteFileName
<< "contents:" << contents.size() << "bytes";
}
Document::Ptr doc = m_snapshot.document(absoluteFileName);
if (doc) {
mergeEnvironment(doc);
return;
}
doc = Document::create(absoluteFileName);
doc->setRevision(m_revision);
doc->setEditorRevision(editorRevision);
if (info.exists())
doc->setLastModified(info.lastModified());
const Document::Ptr previousDoc = switchDocument(doc);
const QByteArray preprocessedCode = m_preprocess.run(absoluteFileName, contents);
// {
// QByteArray b(preprocessedCode);
// b.replace("\n", "<<<\n");
// qDebug("Preprocessed code for \"%s\": [[%s]]", fileName.toUtf8().constData(),
// b.constData());
// }
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(preprocessedCode);
foreach (const Macro ¯o, doc->definedMacros()) {
if (macro.isHidden()) {
static const QByteArray undef("#undef ");
hash.addData(undef);
hash.addData(macro.name());
} else {
static const QByteArray def("#define ");
hash.addData(macro.name());
hash.addData(" ", 1);
hash.addData(def);
hash.addData(macro.definitionText());
}
hash.addData("\n", 1);
}
doc->setFingerprint(hash.result());
Document::Ptr anotherDoc = m_globalSnapshot.document(absoluteFileName);
if (anotherDoc && anotherDoc->fingerprint() == doc->fingerprint()) {
switchDocument(previousDoc);
mergeEnvironment(anotherDoc);
m_snapshot.insert(anotherDoc);
m_todo.remove(absoluteFileName);
return;
}
doc->setUtf8Source(preprocessedCode);
doc->keepSourceAndAST();
doc->tokenize();
m_snapshot.insert(doc);
m_todo.remove(absoluteFileName);
Process process(m_modelManager, doc, m_workingCopy);
process();
(void) switchDocument(previousDoc);
}
Document::Ptr CppPreprocessor::switchDocument(Document::Ptr doc)
{
const Document::Ptr previousDoc = m_currentDoc;
m_currentDoc = doc;
return previousDoc;
}