forked from qt-creator/qt-creator
		
	
		
			
				
	
	
		
			560 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			560 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**************************************************************************
 | |
| **
 | |
| ** This file is part of Qt Creator
 | |
| **
 | |
| ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
 | |
| **
 | |
| ** Contact: Nokia Corporation (qt-info@nokia.com)
 | |
| **
 | |
| ** Commercial Usage
 | |
| **
 | |
| ** Licensees holding valid Qt Commercial licenses may use this file in
 | |
| ** accordance with the Qt Commercial License Agreement provided with the
 | |
| ** Software or, alternatively, in accordance with the terms contained in
 | |
| ** a written agreement between you and Nokia.
 | |
| **
 | |
| ** GNU Lesser General Public License Usage
 | |
| **
 | |
| ** Alternatively, this file may be used under the terms of the GNU Lesser
 | |
| ** General Public License version 2.1 as published by the Free Software
 | |
| ** Foundation and appearing in the file LICENSE.LGPL included in the
 | |
| ** packaging of this file.  Please review the following information to
 | |
| ** ensure the GNU Lesser General Public License version 2.1 requirements
 | |
| ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 | |
| **
 | |
| ** If you are unsure which license is appropriate for your use, please
 | |
| ** contact the sales department at http://qt.nokia.com/contact.
 | |
| **
 | |
| **************************************************************************/
 | |
| 
 | |
| #include "CppDocument.h"
 | |
| #include "CppBindings.h"
 | |
| #include "FastPreprocessor.h"
 | |
| 
 | |
| #include <Control.h>
 | |
| #include <TranslationUnit.h>
 | |
| #include <DiagnosticClient.h>
 | |
| #include <Semantic.h>
 | |
| #include <Literals.h>
 | |
| #include <Symbols.h>
 | |
| #include <AST.h>
 | |
| #include <Scope.h>
 | |
| 
 | |
| #include <QtCore/QByteArray>
 | |
| #include <QtCore/QBitArray>
 | |
| #include <QtCore/QtDebug>
 | |
| 
 | |
| /*!
 | |
|     \namespace CPlusPlus
 | |
|     The namespace for C++ related tools.
 | |
| */
 | |
| 
 | |
| using namespace CPlusPlus;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| class DocumentDiagnosticClient : public DiagnosticClient
 | |
| {
 | |
|     enum { MAX_MESSAGE_COUNT = 10 };
 | |
| 
 | |
| public:
 | |
|     DocumentDiagnosticClient(Document *doc, QList<Document::DiagnosticMessage> *messages)
 | |
|         : doc(doc),
 | |
|           messages(messages),
 | |
|           errorCount(0)
 | |
|     { }
 | |
| 
 | |
|     virtual void report(int level,
 | |
|                         StringLiteral *fileId,
 | |
|                         unsigned line, unsigned column,
 | |
|                         const char *format, va_list ap)
 | |
|     {
 | |
|         if (level == Error) {
 | |
|             ++errorCount;
 | |
| 
 | |
|             if (errorCount >= MAX_MESSAGE_COUNT)
 | |
|                 return; // ignore the error
 | |
|         }
 | |
| 
 | |
|         const QString fileName = QString::fromUtf8(fileId->chars(), fileId->size());
 | |
| 
 | |
|         if (fileName != doc->fileName())
 | |
|             return;
 | |
| 
 | |
|         QString message;
 | |
|         message.vsprintf(format, ap);
 | |
| 
 | |
|         Document::DiagnosticMessage m(convertLevel(level), doc->fileName(),
 | |
|                                       line, column, message);
 | |
|         messages->append(m);
 | |
|     }
 | |
| 
 | |
|     static int convertLevel(int level) {
 | |
|         switch (level) {
 | |
|             case Warning: return Document::DiagnosticMessage::Warning;
 | |
|             case Error:   return Document::DiagnosticMessage::Error;
 | |
|             case Fatal:   return Document::DiagnosticMessage::Fatal;
 | |
|             default:      return Document::DiagnosticMessage::Error;
 | |
|         }
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     Document *doc;
 | |
|     QList<Document::DiagnosticMessage> *messages;
 | |
|     int errorCount;
 | |
| };
 | |
| 
 | |
| } // anonymous namespace
 | |
| 
 | |
| 
 | |
| Document::Document(const QString &fileName)
 | |
|     : _fileName(fileName),
 | |
|       _globalNamespace(0),
 | |
|       _revision(0)
 | |
| {
 | |
|     _control = new Control();
 | |
| 
 | |
|     _control->setDiagnosticClient(new DocumentDiagnosticClient(this, &_diagnosticMessages));
 | |
| 
 | |
|     const QByteArray localFileName = fileName.toUtf8();
 | |
|     StringLiteral *fileId = _control->findOrInsertStringLiteral(localFileName.constData(),
 | |
|                                                                 localFileName.size());
 | |
|     _translationUnit = new TranslationUnit(_control, fileId);
 | |
|     _translationUnit->setQtMocRunEnabled(true);
 | |
|     _translationUnit->setObjCEnabled(true);
 | |
|     (void) _control->switchTranslationUnit(_translationUnit);
 | |
| }
 | |
| 
 | |
| Document::~Document()
 | |
| {
 | |
|     delete _translationUnit;
 | |
|     delete _control->diagnosticClient();
 | |
|     delete _control;
 | |
| }
 | |
| 
 | |
| Control *Document::control() const
 | |
| {
 | |
|     return _control;
 | |
| }
 | |
| 
 | |
| unsigned Document::revision() const
 | |
| {
 | |
|     return _revision;
 | |
| }
 | |
| 
 | |
| void Document::setRevision(unsigned revision)
 | |
| {
 | |
|     _revision = revision;
 | |
| }
 | |
| 
 | |
| QString Document::fileName() const
 | |
| {
 | |
|     return _fileName;
 | |
| }
 | |
| 
 | |
| QStringList Document::includedFiles() const
 | |
| {
 | |
|     QStringList files;
 | |
|     foreach (const Include &i, _includes)
 | |
|         files.append(i.fileName());
 | |
|     files.removeDuplicates();
 | |
|     return files;
 | |
| }
 | |
| 
 | |
| void Document::addIncludeFile(const QString &fileName, unsigned line)
 | |
| {
 | |
|     _includes.append(Include(fileName, line));
 | |
| }
 | |
| 
 | |
| void Document::appendMacro(const Macro ¯o)
 | |
| {
 | |
|     _definedMacros.append(macro);
 | |
| }
 | |
| 
 | |
| void Document::addMacroUse(const Macro ¯o, unsigned offset, unsigned length,
 | |
|                            const QVector<MacroArgumentReference> &actuals, bool inCondition)
 | |
| {
 | |
|     MacroUse use(macro, offset, offset + length);
 | |
|     use.setInCondition(inCondition);
 | |
| 
 | |
|     foreach (const MacroArgumentReference &actual, actuals) {
 | |
|         const Block arg(actual.position(), actual.position() + actual.length());
 | |
| 
 | |
|         use.addArgument(arg);
 | |
|     }
 | |
| 
 | |
|     _macroUses.append(use);
 | |
| }
 | |
| 
 | |
| void Document::addUndefinedMacroUse(const QByteArray &name, unsigned offset)
 | |
| {
 | |
|     QByteArray copy(name.data(), name.size());
 | |
|     UndefinedMacroUse use(copy, offset);
 | |
|     _undefinedMacroUses.append(use);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|     \class Document::MacroUse
 | |
|     \brief Represents the usage of a macro in a \l {Document}.
 | |
|     \sa Document::UndefinedMacroUse
 | |
| */
 | |
| 
 | |
| /*!
 | |
|     \class Document::UndefinedMacroUse
 | |
|     \brief Represents a macro that was looked up, but not found.
 | |
| 
 | |
|     Holds data about the reference to a macro in an \tt{#ifdef} or \tt{#ifndef}
 | |
|     or argument to the \tt{defined} operator inside an \tt{#if} or \tt{#elif} that does
 | |
|     not exist.
 | |
| 
 | |
|     \sa Document::undefinedMacroUses(), Document::MacroUse, Macro
 | |
| */
 | |
| 
 | |
| /*!
 | |
|     \fn QByteArray Document::UndefinedMacroUse::name() const
 | |
| 
 | |
|     Returns the name of the macro that was not found.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|     \fn QList<UndefinedMacroUse> Document::undefinedMacroUses() const
 | |
| 
 | |
|     Returns a list of referenced but undefined macros.
 | |
| 
 | |
|     \sa Document::macroUses(), Document::definedMacros(), Macro
 | |
| */
 | |
| 
 | |
| /*!
 | |
|     \fn QList<MacroUse> Document::macroUses() const
 | |
| 
 | |
|     Returns a list of macros used.
 | |
| 
 | |
|     \sa Document::undefinedMacroUses(), Document::definedMacros(), Macro
 | |
| */
 | |
| 
 | |
| /*!
 | |
|     \fn QList<Macro> Document::definedMacros() const
 | |
| 
 | |
|     Returns the list of macros defined.
 | |
| 
 | |
|     \sa Document::macroUses(), Document::undefinedMacroUses()
 | |
| */
 | |
| 
 | |
| TranslationUnit *Document::translationUnit() const
 | |
| {
 | |
|     return _translationUnit;
 | |
| }
 | |
| 
 | |
| bool Document::skipFunctionBody() const
 | |
| {
 | |
|     return _translationUnit->skipFunctionBody();
 | |
| }
 | |
| 
 | |
| void Document::setSkipFunctionBody(bool skipFunctionBody)
 | |
| {
 | |
|     _translationUnit->setSkipFunctionBody(skipFunctionBody);
 | |
| }
 | |
| 
 | |
| unsigned Document::globalSymbolCount() const
 | |
| {
 | |
|     if (! _globalNamespace)
 | |
|         return 0;
 | |
| 
 | |
|     return _globalNamespace->memberCount();
 | |
| }
 | |
| 
 | |
| Symbol *Document::globalSymbolAt(unsigned index) const
 | |
| {
 | |
|     return _globalNamespace->memberAt(index);
 | |
| }
 | |
| 
 | |
| Scope *Document::globalSymbols() const
 | |
| {
 | |
|     if (! _globalNamespace)
 | |
|         return 0;
 | |
| 
 | |
|     return _globalNamespace->members();
 | |
| }
 | |
| 
 | |
| Namespace *Document::globalNamespace() const
 | |
| {
 | |
|     return _globalNamespace;
 | |
| }
 | |
| 
 | |
| Symbol *Document::findSymbolAt(unsigned line, unsigned column) const
 | |
| {
 | |
|     return findSymbolAt(line, column, globalSymbols());
 | |
| }
 | |
| 
 | |
| Symbol *Document::findSymbolAt(unsigned line, unsigned column, Scope *scope) const
 | |
| {
 | |
|     Symbol *previousSymbol = 0;
 | |
| 
 | |
|     for (unsigned i = 0; i < scope->symbolCount(); ++i) {
 | |
|         Symbol *symbol = scope->symbolAt(i);
 | |
|         if (symbol->line() > line)
 | |
|             break;
 | |
| 
 | |
|         previousSymbol = symbol;
 | |
|     }
 | |
| 
 | |
|     if (previousSymbol) {
 | |
|         if (ScopedSymbol *scoped = previousSymbol->asScopedSymbol()) {
 | |
|             if (Symbol *member = findSymbolAt(line, column, scoped->members()))
 | |
|                 return member;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return previousSymbol;
 | |
| }
 | |
| 
 | |
| Document::Ptr Document::create(const QString &fileName)
 | |
| {
 | |
|     Document::Ptr doc(new Document(fileName));
 | |
|     return doc;
 | |
| }
 | |
| 
 | |
| QByteArray Document::source() const
 | |
| { return _source; }
 | |
| 
 | |
| void Document::setSource(const QByteArray &source)
 | |
| {
 | |
|     _source = source;
 | |
|     _translationUnit->setSource(_source.constBegin(), _source.size());
 | |
| }
 | |
| 
 | |
| void Document::startSkippingBlocks(unsigned start)
 | |
| {
 | |
|     _skippedBlocks.append(Block(start, 0));
 | |
| }
 | |
| 
 | |
| void Document::stopSkippingBlocks(unsigned stop)
 | |
| {
 | |
|     if (_skippedBlocks.isEmpty())
 | |
|         return;
 | |
| 
 | |
|     unsigned start = _skippedBlocks.back().begin();
 | |
|     if (start > stop)
 | |
|         _skippedBlocks.removeLast(); // Ignore this block, it's invalid.
 | |
|     else
 | |
|         _skippedBlocks.back() = Block(start, stop);
 | |
| }
 | |
| 
 | |
| bool Document::isTokenized() const
 | |
| {
 | |
|     return _translationUnit->isTokenized();
 | |
| }
 | |
| 
 | |
| void Document::tokenize()
 | |
| {
 | |
|     _translationUnit->tokenize();
 | |
| }
 | |
| 
 | |
| bool Document::isParsed() const
 | |
| {
 | |
|     return _translationUnit->isParsed();
 | |
| }
 | |
| 
 | |
| bool Document::parse(ParseMode mode)
 | |
| {
 | |
|     TranslationUnit::ParseMode m = TranslationUnit::ParseTranlationUnit;
 | |
|     switch (mode) {
 | |
|     case ParseTranlationUnit:
 | |
|         m = TranslationUnit::ParseTranlationUnit;
 | |
|         break;
 | |
| 
 | |
|     case ParseDeclaration:
 | |
|         m = TranslationUnit::ParseDeclaration;
 | |
|         break;
 | |
| 
 | |
|     case ParseExpression:
 | |
|         m = TranslationUnit::ParseExpression;
 | |
|         break;
 | |
| 
 | |
|     case ParseDeclarator:
 | |
|         m = TranslationUnit::ParseDeclarator;
 | |
|         break;
 | |
| 
 | |
|     case ParseStatement:
 | |
|         m = TranslationUnit::ParseStatement;
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     return _translationUnit->parse(m);
 | |
| }
 | |
| 
 | |
| void Document::check(CheckMode mode)
 | |
| {
 | |
|     Q_ASSERT(!_globalNamespace);
 | |
| 
 | |
|     if (! isParsed())
 | |
|         parse();
 | |
| 
 | |
|     Semantic semantic(_control);
 | |
|     if (mode == FastCheck)
 | |
|         semantic.setSkipFunctionBodies(true);
 | |
| 
 | |
|     _globalNamespace = _control->newNamespace(0);
 | |
|     Scope *globals = _globalNamespace->members();
 | |
|     if (! _translationUnit->ast())
 | |
|         return; // nothing to do.
 | |
| 
 | |
|     if (TranslationUnitAST *ast = _translationUnit->ast()->asTranslationUnit()) {
 | |
|         for (DeclarationListAST *decl = ast->declarations; decl; decl = decl->next) {
 | |
|             semantic.check(decl->declaration, globals);
 | |
|         }
 | |
|     } else if (ExpressionAST *ast = _translationUnit->ast()->asExpression()) {
 | |
|         semantic.check(ast, globals);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Document::releaseSource()
 | |
| {
 | |
|     _source.clear();
 | |
| }
 | |
| 
 | |
| void Document::releaseTranslationUnit()
 | |
| {
 | |
|     _translationUnit->release();
 | |
| }
 | |
| 
 | |
| Snapshot::Snapshot()
 | |
| {
 | |
| }
 | |
| 
 | |
| Snapshot::~Snapshot()
 | |
| {
 | |
| }
 | |
| 
 | |
| void Snapshot::insert(Document::Ptr doc)
 | |
| {
 | |
|     if (doc)
 | |
|         insert(doc->fileName(), doc);
 | |
| }
 | |
| 
 | |
| QByteArray Snapshot::preprocessedCode(const QString &source, const QString &fileName) const
 | |
| {
 | |
|     FastPreprocessor pp(*this);
 | |
|     return pp.run(fileName, source);
 | |
| }
 | |
| 
 | |
| Document::Ptr Snapshot::documentFromSource(const QByteArray &preprocessedCode,
 | |
|                                            const QString &fileName) const
 | |
| {
 | |
|     Document::Ptr newDoc = Document::create(fileName);
 | |
| 
 | |
|     if (Document::Ptr thisDocument = value(fileName)) {
 | |
|         newDoc->_includes = thisDocument->_includes;
 | |
|         newDoc->_definedMacros = thisDocument->_definedMacros;
 | |
|         newDoc->_macroUses = thisDocument->_macroUses;
 | |
|     }
 | |
| 
 | |
|     newDoc->setSource(preprocessedCode);
 | |
|     return newDoc;
 | |
| }
 | |
| 
 | |
| QSharedPointer<NamespaceBinding> Snapshot::globalNamespaceBinding(Document::Ptr doc) const
 | |
| {
 | |
|     return CPlusPlus::bind(doc, *this);
 | |
| }
 | |
| 
 | |
| Snapshot Snapshot::simplified(Document::Ptr doc) const
 | |
| {
 | |
|     Snapshot snapshot;
 | |
|     simplified_helper(doc, &snapshot);
 | |
|     return snapshot;
 | |
| }
 | |
| 
 | |
| void Snapshot::simplified_helper(Document::Ptr doc, Snapshot *snapshot) const
 | |
| {
 | |
|     if (! doc)
 | |
|         return;
 | |
| 
 | |
|     if (! snapshot->contains(doc->fileName())) {
 | |
|         snapshot->insert(doc);
 | |
| 
 | |
|         foreach (const Document::Include &incl, doc->includes()) {
 | |
|             Document::Ptr includedDoc = value(incl.fileName());
 | |
|             simplified_helper(includedDoc, snapshot);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| QStringList Snapshot::dependsOn(const QString &fileName) const
 | |
| {
 | |
|     const int n = size();
 | |
| 
 | |
|     QVector<QString> files(n);
 | |
|     QHash<QString, int> fileIndex;
 | |
|     QHash<int, QList<int> > includes;
 | |
| 
 | |
|     QMapIterator<QString, Document::Ptr> it(*this);
 | |
|     for (int i = 0; it.hasNext(); ++i) {
 | |
|         it.next();
 | |
|         files[i] = it.key();
 | |
|         fileIndex[it.key()] = i;
 | |
|     }
 | |
| 
 | |
|     int index = fileIndex.value(fileName, -1);
 | |
|     if (index == -1) {
 | |
|         qWarning() << fileName << "not in the snapshot";
 | |
|         return QStringList();
 | |
|     }
 | |
| 
 | |
|     QVector<QBitArray> includeMap(files.size());
 | |
| 
 | |
|     for (int i = 0; i < files.size(); ++i) {
 | |
|         if (Document::Ptr doc = value(files.at(i))) {
 | |
|             QBitArray bitmap(files.size());
 | |
|             QList<int> directIncludes;
 | |
| 
 | |
|             foreach (const QString &includedFile, doc->includedFiles()) {
 | |
|                 int index = fileIndex.value(includedFile);
 | |
| 
 | |
|                 if (index == -1)
 | |
|                     continue;
 | |
|                 else if (! directIncludes.contains(index))
 | |
|                     directIncludes.append(index);
 | |
| 
 | |
|                 bitmap.setBit(index, true);
 | |
|             }
 | |
| 
 | |
|             includeMap[i] = bitmap;
 | |
|             includes[i] = directIncludes;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     bool changed;
 | |
| 
 | |
|     do {
 | |
|         changed = false;
 | |
| 
 | |
|         for (int i = 0; i < files.size(); ++i) {
 | |
|             QBitArray bitmap = includeMap.value(i);
 | |
|             QBitArray previousBitmap = bitmap;
 | |
| 
 | |
|             foreach (int includedFileIndex, includes.value(i)) {
 | |
|                 bitmap |= includeMap.value(includedFileIndex);
 | |
|             }
 | |
| 
 | |
|             if (bitmap != previousBitmap) {
 | |
|                 includeMap[i] = bitmap;
 | |
|                 changed = true;
 | |
|             }
 | |
|         }
 | |
|     } while (changed);
 | |
| 
 | |
|     QStringList deps;
 | |
|     for (int i = 0; i < files.size(); ++i) {
 | |
|         const QBitArray &bits = includeMap.at(i);
 | |
| 
 | |
|         if (bits.testBit(index))
 | |
|             deps.append(files.at(i));
 | |
|     }
 | |
| 
 | |
|     return deps;
 | |
| }
 |