forked from qt-creator/qt-creator
Clang: Avoid parse loop if libclang crashed or file vanished
Remember whether clang_parseTranslationUnit() or clang_reparseTranslationUnit() failed the last time and do not trigger parse/reparse again. Also, check whether the main file exists before reparsing. Task-number: QTCREATORBUG-16051 Task-number: QTCREATORBUG-16140 Change-Id: Ied39e66a18032854911229898573941fe2ada35b Reviewed-by: Erik Verbruggen <erik.verbruggen@qt.io>
This commit is contained in:
@@ -12,6 +12,7 @@ HEADERS += $$PWD/clangipcserver.h \
|
||||
$$PWD/projects.h \
|
||||
$$PWD/translationunits.h \
|
||||
$$PWD/translationunitparseerrorexception.h \
|
||||
$$PWD/translationunitreparseerrorexception.h \
|
||||
$$PWD/projectpart.h \
|
||||
$$PWD/translationunitfilenotexitexception.h \
|
||||
$$PWD/translationunitdoesnotexistexception.h \
|
||||
@@ -46,6 +47,7 @@ SOURCES += $$PWD/clangipcserver.cpp \
|
||||
$$PWD/projects.cpp \
|
||||
$$PWD/translationunits.cpp \
|
||||
$$PWD/translationunitparseerrorexception.cpp \
|
||||
$$PWD/translationunitreparseerrorexception.cpp \
|
||||
$$PWD/projectpart.cpp \
|
||||
$$PWD/translationunitfilenotexitexception.cpp \
|
||||
$$PWD/translationunitdoesnotexistexception.cpp \
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "translationunitfilenotexitexception.h"
|
||||
#include "translationunitisnullexception.h"
|
||||
#include "translationunitparseerrorexception.h"
|
||||
#include "translationunitreparseerrorexception.h"
|
||||
#include "translationunits.h"
|
||||
#include "unsavedfiles.h"
|
||||
|
||||
@@ -76,6 +77,8 @@ public:
|
||||
Utf8StringVector fileArguments;
|
||||
Utf8String filePath;
|
||||
CXTranslationUnit translationUnit = nullptr;
|
||||
CXErrorCode parseErrorCode = CXError_Success;
|
||||
int reparseErrorCode = 0;
|
||||
CXIndex index = nullptr;
|
||||
uint documentRevision = 0;
|
||||
bool needsToBeReparsed = false;
|
||||
@@ -155,6 +158,16 @@ void TranslationUnit::reparse() const
|
||||
reparseTranslationUnit();
|
||||
}
|
||||
|
||||
bool TranslationUnit::parseWasSuccessful() const
|
||||
{
|
||||
return d->parseErrorCode == CXError_Success;
|
||||
}
|
||||
|
||||
bool TranslationUnit::reparseWasSuccessful() const
|
||||
{
|
||||
return d->reparseErrorCode == 0;
|
||||
}
|
||||
|
||||
CXIndex TranslationUnit::index() const
|
||||
{
|
||||
checkIfNull();
|
||||
@@ -350,7 +363,7 @@ void TranslationUnit::checkIfNull() const
|
||||
|
||||
void TranslationUnit::checkIfFileExists() const
|
||||
{
|
||||
if (!QFileInfo::exists(d->filePath.toString()))
|
||||
if (!fileExists())
|
||||
throw TranslationUnitFileNotExitsException(d->filePath);
|
||||
}
|
||||
|
||||
@@ -396,16 +409,16 @@ void TranslationUnit::createTranslationUnitIfNeeded() const
|
||||
if (isVerboseModeEnabled())
|
||||
args.print();
|
||||
|
||||
CXErrorCode errorCode = clang_parseTranslationUnit2(index(),
|
||||
NULL,
|
||||
args.data(),
|
||||
args.count(),
|
||||
unsavedFiles().cxUnsavedFiles(),
|
||||
unsavedFiles().count(),
|
||||
defaultOptions(),
|
||||
&d->translationUnit);
|
||||
d->parseErrorCode = clang_parseTranslationUnit2(index(),
|
||||
NULL,
|
||||
args.data(),
|
||||
args.count(),
|
||||
unsavedFiles().cxUnsavedFiles(),
|
||||
unsavedFiles().count(),
|
||||
defaultOptions(),
|
||||
&d->translationUnit);
|
||||
|
||||
checkTranslationUnitErrorCode(errorCode);
|
||||
checkParseErrorCode();
|
||||
|
||||
updateIncludeFilePaths();
|
||||
|
||||
@@ -413,22 +426,33 @@ void TranslationUnit::createTranslationUnitIfNeeded() const
|
||||
}
|
||||
}
|
||||
|
||||
void TranslationUnit::checkTranslationUnitErrorCode(CXErrorCode errorCode) const
|
||||
void TranslationUnit::checkParseErrorCode() const
|
||||
{
|
||||
switch (errorCode) {
|
||||
case CXError_Success: break;
|
||||
default: throw TranslationUnitParseErrorException(d->filePath,
|
||||
d->projectPart.projectPartId(),
|
||||
errorCode);
|
||||
if (!parseWasSuccessful()) {
|
||||
throw TranslationUnitParseErrorException(d->filePath,
|
||||
d->projectPart.projectPartId(),
|
||||
d->parseErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
void TranslationUnit::checkReparseErrorCode() const
|
||||
{
|
||||
if (!reparseWasSuccessful()) {
|
||||
throw TranslationUnitReparseErrorException(d->filePath,
|
||||
d->projectPart.projectPartId(),
|
||||
d->reparseErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
void TranslationUnit::reparseTranslationUnit() const
|
||||
{
|
||||
clang_reparseTranslationUnit(d->translationUnit,
|
||||
unsavedFiles().count(),
|
||||
unsavedFiles().cxUnsavedFiles(),
|
||||
clang_defaultReparseOptions(d->translationUnit));
|
||||
d->reparseErrorCode = clang_reparseTranslationUnit(
|
||||
d->translationUnit,
|
||||
unsavedFiles().count(),
|
||||
unsavedFiles().cxUnsavedFiles(),
|
||||
clang_defaultReparseOptions(d->translationUnit));
|
||||
|
||||
checkReparseErrorCode();
|
||||
|
||||
updateIncludeFilePaths();
|
||||
|
||||
@@ -474,6 +498,19 @@ void TranslationUnit::updateIncludeFilePaths() const
|
||||
d->translationUnits.addWatchedFiles(d->dependedFilePaths);
|
||||
}
|
||||
|
||||
bool TranslationUnit::fileExists() const
|
||||
{
|
||||
return QFileInfo::exists(d->filePath.toString());
|
||||
}
|
||||
|
||||
bool TranslationUnit::isIntact() const
|
||||
{
|
||||
return !isNull()
|
||||
&& fileExists()
|
||||
&& parseWasSuccessful()
|
||||
&& reparseWasSuccessful();
|
||||
}
|
||||
|
||||
CommandLineArguments TranslationUnit::commandLineArguments() const
|
||||
{
|
||||
return CommandLineArguments(d->filePath.constData(),
|
||||
|
||||
@@ -91,6 +91,8 @@ public:
|
||||
void reset();
|
||||
void reparse() const;
|
||||
|
||||
bool isIntact() const;
|
||||
|
||||
CXIndex index() const;
|
||||
CXTranslationUnit cxTranslationUnit() const;
|
||||
CXTranslationUnit cxTranslationUnitWithoutReparsing() const;
|
||||
@@ -150,10 +152,14 @@ private:
|
||||
bool projectPartIsOutdated() const;
|
||||
bool isMainFileAndExistsOrIsOtherFile(const Utf8String &filePath) const;
|
||||
void createTranslationUnitIfNeeded() const;
|
||||
void checkTranslationUnitErrorCode(CXErrorCode errorCode) const;
|
||||
void checkParseErrorCode() const;
|
||||
void checkReparseErrorCode() const;
|
||||
void reparseTranslationUnit() const;
|
||||
void reparseTranslationUnitIfFilesAreChanged() const;
|
||||
bool parseWasSuccessful() const;
|
||||
bool reparseWasSuccessful() const;
|
||||
void updateIncludeFilePaths() const;
|
||||
bool fileExists() const;
|
||||
static void includeCallback(CXFile included_file,
|
||||
CXSourceLocation * /*inclusion_stack*/,
|
||||
unsigned /*include_len*/,
|
||||
|
||||
@@ -65,12 +65,13 @@ static const char *errorCodeToText(CXErrorCode errorCode)
|
||||
const char *TranslationUnitParseErrorException::what() const Q_DECL_NOEXCEPT
|
||||
{
|
||||
if (what_.isEmpty()) {
|
||||
what_ += Utf8StringLiteral("Parse error for file ")
|
||||
what_ += Utf8StringLiteral("clang_parseTranslationUnit() failed for file ")
|
||||
+ filePath()
|
||||
+ Utf8StringLiteral(" in project ")
|
||||
+ projectPartId()
|
||||
+ Utf8StringLiteral(": ")
|
||||
+ Utf8String::fromUtf8(errorCodeToText(errorCode_));
|
||||
+ Utf8String::fromUtf8(errorCodeToText(errorCode_))
|
||||
+ Utf8StringLiteral(".");
|
||||
}
|
||||
|
||||
return what_.constData();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "translationunitreparseerrorexception.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace ClangBackEnd {
|
||||
|
||||
TranslationUnitReparseErrorException::TranslationUnitReparseErrorException(
|
||||
const Utf8String &filePath,
|
||||
const Utf8String &projectPartId,
|
||||
int errorCode)
|
||||
: filePath_(filePath),
|
||||
projectPartId_(projectPartId),
|
||||
errorCode_(errorCode)
|
||||
{
|
||||
}
|
||||
|
||||
const Utf8String &TranslationUnitReparseErrorException::filePath() const
|
||||
{
|
||||
return filePath_;
|
||||
}
|
||||
|
||||
const Utf8String &TranslationUnitReparseErrorException::projectPartId() const
|
||||
{
|
||||
return projectPartId_;
|
||||
}
|
||||
|
||||
const char *TranslationUnitReparseErrorException::what() const Q_DECL_NOEXCEPT
|
||||
{
|
||||
if (what_.isEmpty()) {
|
||||
what_ += Utf8StringLiteral("clang_reparseTranslationUnit() failed for file ")
|
||||
+ filePath()
|
||||
+ Utf8StringLiteral(" in project ")
|
||||
+ projectPartId()
|
||||
+ Utf8StringLiteral(": ")
|
||||
+ Utf8String::fromString(QString::number(errorCode_))
|
||||
+ Utf8StringLiteral(".");
|
||||
}
|
||||
|
||||
return what_.constData();
|
||||
}
|
||||
|
||||
} // namespace ClangBackEnd
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utf8string.h>
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace ClangBackEnd {
|
||||
|
||||
class TranslationUnitReparseErrorException : public std::exception
|
||||
{
|
||||
public:
|
||||
TranslationUnitReparseErrorException(const Utf8String &filePath,
|
||||
const Utf8String &projectPartId,
|
||||
int errorCode);
|
||||
|
||||
const Utf8String &filePath() const;
|
||||
const Utf8String &projectPartId() const;
|
||||
|
||||
const char *what() const Q_DECL_NOEXCEPT override;
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# if !__GNUC_PREREQ(4,8)
|
||||
~TranslationUnitReparseErrorException() noexcept {}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
private:
|
||||
Utf8String filePath_;
|
||||
Utf8String projectPartId_;
|
||||
int errorCode_;
|
||||
mutable Utf8String what_;
|
||||
};
|
||||
|
||||
} // namespace ClangBackEnd
|
||||
@@ -199,7 +199,9 @@ namespace {
|
||||
|
||||
bool translationUnitHasNewDocumentAnnotations(const TranslationUnit &translationUnit)
|
||||
{
|
||||
return translationUnit.hasNewDiagnostics() || translationUnit.hasNewHighlightingInformations();
|
||||
return translationUnit.isIntact()
|
||||
&& (translationUnit.hasNewDiagnostics()
|
||||
|| translationUnit.hasNewHighlightingInformations());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include <unsavedfiles.h>
|
||||
#include <utf8string.h>
|
||||
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include "mocksenddocumentannotationscallback.h"
|
||||
@@ -83,6 +85,7 @@ class TranslationUnits : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override;
|
||||
void createDirtyTranslationUnitAndDeleteFile();
|
||||
void sendAllDocumentAnnotations();
|
||||
void sendAllDocumentAnnotationsForCurrentEditor();
|
||||
void sendAllDocumentAnnotationsForVisibleEditors();
|
||||
@@ -130,6 +133,15 @@ TEST_F(TranslationUnits, DoNotThrowForAddingNonExistingFileWithUnsavedContent)
|
||||
ASSERT_NO_THROW(translationUnits.create({fileContainer}));
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnits, DoNotSendDocumentAnnotationsForVanishedMainFile)
|
||||
{
|
||||
createDirtyTranslationUnitAndDeleteFile();
|
||||
|
||||
EXPECT_CALL(mockSendDocumentAnnotationsCallback, sendDocumentAnnotations()).Times(0);
|
||||
|
||||
sendAllDocumentAnnotations();
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnits, Add)
|
||||
{
|
||||
ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u);
|
||||
@@ -504,6 +516,20 @@ void TranslationUnits::SetUp()
|
||||
translationUnits.setSendDocumentAnnotationsCallback(callback);
|
||||
}
|
||||
|
||||
void TranslationUnits::createDirtyTranslationUnitAndDeleteFile()
|
||||
{
|
||||
QTemporaryFile temporaryFile(QLatin1String("XXXXXX.cpp"));
|
||||
EXPECT_TRUE(temporaryFile.open());
|
||||
const QString temporaryFilePath = Utf8String::fromString(temporaryFile.fileName());
|
||||
|
||||
ClangBackEnd::FileContainer fileContainer(temporaryFilePath,
|
||||
projectPartId, Utf8String(), true);
|
||||
translationUnits.create({fileContainer});
|
||||
auto translationUnit = translationUnits.translationUnit(fileContainer);
|
||||
translationUnit.setIsUsedByCurrentEditor(true);
|
||||
translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath());
|
||||
}
|
||||
|
||||
void TranslationUnits::sendAllDocumentAnnotations()
|
||||
{
|
||||
auto sendState = DocumentAnnotationsSendState::MaybeThereAreDocumentAnnotations;
|
||||
|
||||
@@ -72,7 +72,7 @@ class TranslationUnit : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override;
|
||||
::TranslationUnit createTemporaryTranslationUnit();
|
||||
::TranslationUnit createTranslationUnitAndDeleteFile();
|
||||
QByteArray readContentFromTranslationUnitFile() const;
|
||||
|
||||
protected:
|
||||
@@ -92,6 +92,13 @@ TEST_F(TranslationUnit, DefaultTranslationUnitIsInvalid)
|
||||
ASSERT_TRUE(translationUnit.isNull());
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnit, DefaultTranslationUnitIsNotIntact)
|
||||
{
|
||||
::TranslationUnit translationUnit;
|
||||
|
||||
ASSERT_FALSE(translationUnit.isIntact());
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnit, ThrowExceptionForNonExistingFilePath)
|
||||
{
|
||||
ASSERT_THROW(::TranslationUnit(Utf8StringLiteral("file.cpp"), projectPart, Utf8StringVector(), translationUnits),
|
||||
@@ -193,7 +200,7 @@ TEST_F(TranslationUnit, DependedFilePaths)
|
||||
|
||||
TEST_F(TranslationUnit, DeletedFileShouldNotNeedReparsing)
|
||||
{
|
||||
auto translationUnit = createTemporaryTranslationUnit();
|
||||
auto translationUnit = createTranslationUnitAndDeleteFile();
|
||||
|
||||
translationUnit.setDirtyIfDependencyIsMet(translationUnit.filePath());
|
||||
|
||||
@@ -244,6 +251,20 @@ TEST_F(TranslationUnit, NeedsNoReparsingAfterReparsing)
|
||||
ASSERT_FALSE(translationUnit.isNeedingReparse());
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnit, IsIntactAfterCreation)
|
||||
{
|
||||
translationUnit.cxTranslationUnit();
|
||||
|
||||
ASSERT_TRUE(translationUnit.isIntact());
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnit, IsNotIntactForDeletedFile)
|
||||
{
|
||||
auto translationUnit = createTranslationUnitAndDeleteFile();
|
||||
|
||||
ASSERT_FALSE(translationUnit.isIntact());
|
||||
}
|
||||
|
||||
TEST_F(TranslationUnit, HasNewDiagnosticsAfterCreation)
|
||||
{
|
||||
translationUnit.cxTranslationUnit();
|
||||
@@ -364,7 +385,7 @@ void TranslationUnit::SetUp()
|
||||
translationUnit = createdTranslationUnits.front();
|
||||
}
|
||||
|
||||
::TranslationUnit TranslationUnit::createTemporaryTranslationUnit()
|
||||
::TranslationUnit TranslationUnit::createTranslationUnitAndDeleteFile()
|
||||
{
|
||||
QTemporaryFile temporaryFile;
|
||||
EXPECT_TRUE(temporaryFile.open());
|
||||
|
||||
Reference in New Issue
Block a user