diff options
author | Nikolai Kosjar <nikolai.kosjar@digia.com> | 2014-05-16 15:51:04 -0400 |
---|---|---|
committer | Nikolai Kosjar <nikolai.kosjar@digia.com> | 2014-06-04 16:28:35 +0200 |
commit | ec97d967e63e4c66e0147625b09a658a3f99896c (patch) | |
tree | 77c44744474915153d08b550dcf2b4bedd3f3516 /src/plugins/cpptools/cppsourceprocessor.cpp | |
parent | 43895b038f1712d4c98b8f81d7248b44f42f27cd (diff) | |
download | qt-creator-ec97d967e63e4c66e0147625b09a658a3f99896c.tar.gz |
CppTools: Rename CppPreprocessor to CppSourceProcessor
...since it does quite a bit more than only preprocessing, as the name
suggests. We use that class to process source files in general. The
output is not a preprocessed source, but a set of CPlusPlus::Documents
with symbols.
Change-Id: I787d0f22f9f042ddf0c99e8c2f0bdb9aa7001735
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
Diffstat (limited to 'src/plugins/cpptools/cppsourceprocessor.cpp')
-rw-r--r-- | src/plugins/cpptools/cppsourceprocessor.cpp | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/src/plugins/cpptools/cppsourceprocessor.cpp b/src/plugins/cpptools/cppsourceprocessor.cpp new file mode 100644 index 0000000000..5f5f12bc63 --- /dev/null +++ b/src/plugins/cpptools/cppsourceprocessor.cpp @@ -0,0 +1,502 @@ +#include "cppsourceprocessor.h" + +#include "cppmodelmanager.h" + +#include <coreplugin/editormanager/editormanager.h> + +#include <utils/fileutils.h> +#include <utils/hostosinfo.h> +#include <utils/textfileformat.h> + +#include <QCoreApplication> +#include <QCryptographicHash> +#include <QTextCodec> + +/*! + * \class CppTools::Internal::CppSourceProcessor + * \brief The CppSourceProcessor 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; + +CppSourceProcessor::CppSourceProcessor(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); +} + +CppSourceProcessor::CppSourceProcessor(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); +} + +CppSourceProcessor::~CppSourceProcessor() +{ } + +void CppSourceProcessor::setRevision(unsigned revision) +{ m_revision = revision; } + +void CppSourceProcessor::setWorkingCopy(const CppModelManagerInterface::WorkingCopy &workingCopy) +{ m_workingCopy = workingCopy; } + +void CppSourceProcessor::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 CppSourceProcessor::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 CppSourceProcessor::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")); + if (privateFrameworks.exists() && privateFrameworks.isDir()) + addFrameworkPath(privateFrameworks.absoluteFilePath()); + } +} + +void CppSourceProcessor::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 CppSourceProcessor::run(const QString &fileName) +{ + sourceNeeded(0, fileName, IncludeGlobal); +} + +void CppSourceProcessor::removeFromCache(const QString &fileName) +{ + m_snapshot.remove(fileName); +} + +void CppSourceProcessor::resetEnvironment() +{ + m_env.reset(); + m_processed.clear(); + m_included.clear(); +} + +bool CppSourceProcessor::getFileContents(const QString &absoluteFilePath, + QByteArray *contents, + unsigned *revision) const +{ + if (absoluteFilePath.isEmpty() || !contents || !revision) + return false; + + // Get from working copy + if (m_workingCopy.contains(absoluteFilePath)) { + const QPair<QByteArray, unsigned> entry = m_workingCopy.get(absoluteFilePath); + *contents = entry.first; + *revision = entry.second; + return true; + } + + // Get from file + *revision = 0; + QString error; + if (Utils::TextFileFormat::readFileUTF8(absoluteFilePath, m_defaultCodec, contents, &error) + != Utils::TextFileFormat::ReadSuccess) { + qWarning("Error reading file \"%s\": \"%s\".", qPrintable(absoluteFilePath), + qPrintable(error)); + return false; + } + return true; +} + +bool CppSourceProcessor::checkFile(const QString &absoluteFilePath) const +{ + if (absoluteFilePath.isEmpty() + || m_included.contains(absoluteFilePath) + || m_workingCopy.contains(absoluteFilePath)) { + return true; + } + + const QFileInfo fileInfo(absoluteFilePath); + return fileInfo.isFile() && fileInfo.isReadable(); +} + +/// Resolve the given file name to its absolute path w.r.t. the include type. +QString CppSourceProcessor::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 CppSourceProcessor::cleanPath(const QString &path) +{ + QString result = QDir::cleanPath(path); + const QChar slash(QLatin1Char('/')); + if (!result.endsWith(slash)) + result.append(slash); + return result; +} + +QString CppSourceProcessor::resolveFile_helper(const QString &fileName, IncludeType type) +{ + if (isInjectedFile(fileName)) + return fileName; + + if (QFileInfo(fileName).isAbsolute()) + return checkFile(fileName) ? fileName : QString(); + + 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('/')); + if (index != -1) { + 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 CppSourceProcessor::macroAdded(const Macro ¯o) +{ + if (!m_currentDoc) + 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 CppSourceProcessor::passedMacroDefinitionCheck(unsigned bytesOffset, unsigned utf16charsOffset, + unsigned line, const Macro ¯o) +{ + if (!m_currentDoc) + return; + + m_currentDoc->addMacroUse(revision(m_workingCopy, macro), + bytesOffset, macro.name().length(), + utf16charsOffset, macro.nameToQString().size(), + line, QVector<MacroArgumentReference>()); +} + +void CppSourceProcessor::failedMacroDefinitionCheck(unsigned bytesOffset, unsigned utf16charOffset, + const ByteArrayRef &name) +{ + if (!m_currentDoc) + return; + + m_currentDoc->addUndefinedMacroUse(QByteArray(name.start(), name.size()), + bytesOffset, utf16charOffset); +} + +void CppSourceProcessor::notifyMacroReference(unsigned bytesOffset, unsigned utf16charOffset, + unsigned line, const Macro ¯o) +{ + if (!m_currentDoc) + return; + + m_currentDoc->addMacroUse(revision(m_workingCopy, macro), + bytesOffset, macro.name().length(), + utf16charOffset, macro.nameToQString().size(), + line, QVector<MacroArgumentReference>()); +} + +void CppSourceProcessor::startExpandingMacro(unsigned bytesOffset, unsigned utf16charOffset, + unsigned line, const Macro ¯o, + const QVector<MacroArgumentReference> &actuals) +{ + if (!m_currentDoc) + return; + + m_currentDoc->addMacroUse(revision(m_workingCopy, macro), + bytesOffset, macro.name().length(), + utf16charOffset, macro.nameToQString().size(), + line, actuals); +} + +void CppSourceProcessor::stopExpandingMacro(unsigned, const Macro &) +{ + if (!m_currentDoc) + return; +} + +void CppSourceProcessor::markAsIncludeGuard(const QByteArray ¯oName) +{ + if (!m_currentDoc) + return; + + m_currentDoc->setIncludeGuardMacroName(macroName); +} + +void CppSourceProcessor::mergeEnvironment(Document::Ptr doc) +{ + if (!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 if (!m_included.contains(includedFile)) + run(includedFile); + } + + m_env.addMacros(doc->definedMacros()); +} + +void CppSourceProcessor::startSkippingBlocks(unsigned utf16charsOffset) +{ + if (m_currentDoc) + m_currentDoc->startSkippingBlocks(utf16charsOffset); +} + +void CppSourceProcessor::stopSkippingBlocks(unsigned utf16charsOffset) +{ + if (m_currentDoc) + m_currentDoc->stopSkippingBlocks(utf16charsOffset); +} + +void CppSourceProcessor::sourceNeeded(unsigned line, const QString &fileName, IncludeType type) +{ + typedef Document::DiagnosticMessage Message; + 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 (absoluteFileName.isEmpty()) { + const QString text = QCoreApplication::translate( + "CppSourceProcessor", "%1: No such file or directory").arg(fileName); + Message message(Message::Warning, m_currentDoc->fileName(), line, /*column =*/ 0, text); + m_currentDoc->addDiagnosticMessage(message); + return; + } + } + if (m_included.contains(absoluteFileName)) + return; // we've already seen this file. + if (absoluteFileName != modelManager()->configurationFileName()) + m_included.insert(absoluteFileName); + + // Already in snapshot? Use it! + Document::Ptr doc = m_snapshot.document(absoluteFileName); + if (doc) { + mergeEnvironment(doc); + return; + } + + // Otherwise get file contents + unsigned editorRevision = 0; + QByteArray contents; + const bool gotFileContents = getFileContents(absoluteFileName, &contents, &editorRevision); + if (m_currentDoc && !gotFileContents) { + const QString text = QCoreApplication::translate( + "CppSourceProcessor", "%1: Could not get file contents").arg(fileName); + Message message(Message::Warning, m_currentDoc->fileName(), line, /*column =*/ 0, text); + m_currentDoc->addDiagnosticMessage(message); + return; + } + + if (m_dumpFileNameWhileParsing) { + qDebug() << "Parsing file:" << absoluteFileName + << "contents:" << contents.size() << "bytes"; + } + + doc = Document::create(absoluteFileName); + doc->setRevision(m_revision); + doc->setEditorRevision(editorRevision); + + const QFileInfo info(absoluteFileName); + 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()); +// } + + 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 CppSourceProcessor::switchDocument(Document::Ptr doc) +{ + const Document::Ptr previousDoc = m_currentDoc; + m_currentDoc = doc; + return previousDoc; +} |