1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "clangbackendcommunicator.h"
27 
28 #include "clangbackendlogging.h"
29 #include "clangcompletionassistprocessor.h"
30 #include "clangmodelmanagersupport.h"
31 #include "clangutils.h"
32 
33 #include <coreplugin/editormanager/editormanager.h>
34 #include <coreplugin/icore.h>
35 #include <coreplugin/messagemanager.h>
36 
37 #include <cpptools/abstracteditorsupport.h>
38 #include <cpptools/baseeditordocumentprocessor.h>
39 #include <cpptools/cppmodelmanager.h>
40 #include <cpptools/editordocumenthandle.h>
41 #include <cpptools/projectinfo.h>
42 #include <cpptools/cpptoolsbridge.h>
43 
44 #include <texteditor/codeassist/functionhintproposal.h>
45 #include <texteditor/codeassist/iassistprocessor.h>
46 #include <texteditor/texteditor.h>
47 
48 #include <clangsupport/filecontainer.h>
49 #include <clangsupport/clangcodemodelservermessages.h>
50 
51 #include <utils/globalfilechangeblocker.h>
52 #include <utils/qtcassert.h>
53 
54 #include <QDateTime>
55 #include <QDir>
56 #include <QTextBlock>
57 
58 using namespace CPlusPlus;
59 using namespace ClangBackEnd;
60 using namespace TextEditor;
61 using namespace Utils;
62 
63 enum { backEndStartTimeOutInMs = 10000 };
64 
backendProcessPath()65 static QString backendProcessPath()
66 {
67     return Core::ICore::libexecPath("clangbackend" QTC_HOST_EXE_SUFFIX).toString();
68 }
69 
70 namespace ClangCodeModel {
71 namespace Internal {
72 
73 class DummyBackendSender : public ClangBackEnd::ClangCodeModelServerInterface
74 {
75 public:
end()76     void end() override {}
77 
documentsOpened(const DocumentsOpenedMessage &)78     void documentsOpened(const DocumentsOpenedMessage &) override {}
documentsChanged(const DocumentsChangedMessage &)79     void documentsChanged(const DocumentsChangedMessage &) override {}
documentsClosed(const DocumentsClosedMessage &)80     void documentsClosed(const DocumentsClosedMessage &) override {}
documentVisibilityChanged(const DocumentVisibilityChangedMessage &)81     void documentVisibilityChanged(const DocumentVisibilityChangedMessage &) override {}
82 
unsavedFilesUpdated(const UnsavedFilesUpdatedMessage &)83     void unsavedFilesUpdated(const UnsavedFilesUpdatedMessage &) override {}
unsavedFilesRemoved(const UnsavedFilesRemovedMessage &)84     void unsavedFilesRemoved(const UnsavedFilesRemovedMessage &) override {}
85 
requestCompletions(const RequestCompletionsMessage &)86     void requestCompletions(const RequestCompletionsMessage &) override {}
requestAnnotations(const RequestAnnotationsMessage &)87     void requestAnnotations(const RequestAnnotationsMessage &) override {}
requestReferences(const RequestReferencesMessage &)88     void requestReferences(const RequestReferencesMessage &) override {}
requestFollowSymbol(const RequestFollowSymbolMessage &)89     void requestFollowSymbol(const RequestFollowSymbolMessage &) override {}
requestToolTip(const RequestToolTipMessage &)90     void requestToolTip(const RequestToolTipMessage &) override {}
91 };
92 
BackendCommunicator()93 BackendCommunicator::BackendCommunicator()
94     : m_connection(&m_receiver)
95     , m_sender(new DummyBackendSender())
96 {
97     m_backendStartTimeOut.setSingleShot(true);
98     connect(&m_backendStartTimeOut, &QTimer::timeout,
99             this, &BackendCommunicator::logStartTimeOut);
100 
101     m_receiver.setAliveHandler([this]() { m_connection.resetProcessAliveTimer(); });
102 
103     connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose,
104             this, &BackendCommunicator::onEditorAboutToClose);
105     connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose,
106             this, &BackendCommunicator::setupDummySender);
107     auto globalFCB = GlobalFileChangeBlocker::instance();
108     m_postponeBackendJobs = globalFCB->isBlocked();
109     connect(globalFCB, &GlobalFileChangeBlocker::stateChanged,
110             this, &BackendCommunicator::setBackendJobsPostponed);
111 
112     initializeBackend();
113 }
114 
~BackendCommunicator()115 BackendCommunicator::~BackendCommunicator()
116 {
117     disconnect(&m_connection, nullptr, this, nullptr);
118 }
119 
initializeBackend()120 void BackendCommunicator::initializeBackend()
121 {
122     const QString clangBackEndProcessPath = backendProcessPath();
123     if (!QFileInfo::exists(clangBackEndProcessPath)) {
124         logExecutableDoesNotExist();
125         return;
126     }
127     qCDebug(ipcLog) << "Starting" << clangBackEndProcessPath;
128 
129     m_connection.setProcessAliveTimerInterval(30 * 1000);
130     m_connection.setProcessPath(clangBackEndProcessPath);
131 
132     connect(&m_connection, &ConnectionClient::connectedToLocalSocket,
133             this, &BackendCommunicator::onConnectedToBackend);
134     connect(&m_connection, &ConnectionClient::disconnectedFromLocalSocket,
135             this, &BackendCommunicator::setupDummySender);
136 
137     m_connection.startProcessAndConnectToServerAsynchronously();
138     m_backendStartTimeOut.start(backEndStartTimeOutInMs);
139 }
140 
141 namespace {
removeDuplicates(Utf8StringVector & visibleEditorDocumentsFilePaths)142 void removeDuplicates(Utf8StringVector &visibleEditorDocumentsFilePaths)
143 {
144     std::sort(visibleEditorDocumentsFilePaths.begin(),
145               visibleEditorDocumentsFilePaths.end());
146     const auto end = std::unique(visibleEditorDocumentsFilePaths.begin(),
147                                  visibleEditorDocumentsFilePaths.end());
148     visibleEditorDocumentsFilePaths.erase(end,
149                                           visibleEditorDocumentsFilePaths.end());
150 }
151 
removeNonCppEditors(QList<Core::IEditor * > & visibleEditors)152 void removeNonCppEditors(QList<Core::IEditor*> &visibleEditors)
153 {
154     const auto isNotCppEditor = [] (Core::IEditor *editor) {
155         return !CppTools::CppModelManager::isCppEditor(editor);
156     };
157 
158     const auto end = std::remove_if(visibleEditors.begin(),
159                                     visibleEditors.end(),
160                                     isNotCppEditor);
161 
162     visibleEditors.erase(end, visibleEditors.end());
163 }
164 
visibleCppEditorDocumentsFilePaths()165 Utf8StringVector visibleCppEditorDocumentsFilePaths()
166 {
167     auto visibleEditors = CppTools::CppToolsBridge::visibleEditors();
168 
169     removeNonCppEditors(visibleEditors);
170 
171     Utf8StringVector visibleCppEditorDocumentsFilePaths;
172     visibleCppEditorDocumentsFilePaths.reserve(visibleEditors.size());
173 
174     const auto editorFilePaths = [] (Core::IEditor *editor) {
175         return Utf8String(editor->document()->filePath().toString());
176     };
177 
178     std::transform(visibleEditors.begin(),
179                    visibleEditors.end(),
180                    std::back_inserter(visibleCppEditorDocumentsFilePaths),
181                    editorFilePaths);
182 
183     removeDuplicates(visibleCppEditorDocumentsFilePaths);
184 
185     return visibleCppEditorDocumentsFilePaths;
186 }
187 
188 }
189 
documentVisibilityChanged()190 void BackendCommunicator::documentVisibilityChanged()
191 {
192     documentVisibilityChanged(currentCppEditorDocumentFilePath(),
193                               visibleCppEditorDocumentsFilePaths());
194 }
195 
isNotWaitingForCompletion() const196 bool BackendCommunicator::isNotWaitingForCompletion() const
197 {
198     return !m_receiver.isExpectingCompletionsMessage();
199 }
200 
setBackendJobsPostponed(bool postponed)201 void BackendCommunicator::setBackendJobsPostponed(bool postponed)
202 {
203     if (postponed) {
204         documentVisibilityChanged(Utf8String(), {});
205         m_postponeBackendJobs = postponed;
206     } else {
207         m_postponeBackendJobs = postponed;
208         documentVisibilityChanged();
209     }
210 }
211 
documentVisibilityChanged(const Utf8String & currentEditorFilePath,const Utf8StringVector & visibleEditorsFilePaths)212 void BackendCommunicator::documentVisibilityChanged(const Utf8String &currentEditorFilePath,
213                                                     const Utf8StringVector &visibleEditorsFilePaths)
214 {
215     if (m_postponeBackendJobs)
216         return;
217 
218     const DocumentVisibilityChangedMessage message(currentEditorFilePath, visibleEditorsFilePaths);
219     m_sender->documentVisibilityChanged(message);
220 }
221 
restoreCppEditorDocuments()222 void BackendCommunicator::restoreCppEditorDocuments()
223 {
224     resetCppEditorDocumentProcessors();
225     CppTools::CppModelManager::instance()->updateCppEditorDocuments();
226 }
227 
resetCppEditorDocumentProcessors()228 void BackendCommunicator::resetCppEditorDocumentProcessors()
229 {
230     using namespace CppTools;
231 
232     const auto cppEditorDocuments = CppModelManager::instance()->cppEditorDocuments();
233     foreach (CppEditorDocumentHandle *cppEditorDocument, cppEditorDocuments)
234         cppEditorDocument->resetProcessor();
235 }
236 
unsavedFilesUpdatedForUiHeaders()237 void BackendCommunicator::unsavedFilesUpdatedForUiHeaders()
238 {
239     using namespace CppTools;
240 
241     const auto editorSupports = CppModelManager::instance()->abstractEditorSupports();
242     foreach (const AbstractEditorSupport *es, editorSupports) {
243         const QString mappedPath
244                 = ClangModelManagerSupport::instance()->dummyUiHeaderOnDiskPath(es->fileName());
245         unsavedFilesUpdated(mappedPath, es->contents(), es->revision());
246     }
247 }
248 
documentsChangedFromCppEditorDocument(const QString & filePath)249 void BackendCommunicator::documentsChangedFromCppEditorDocument(const QString &filePath)
250 {
251     const CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
252     QTC_ASSERT(document, return);
253     documentsChanged(filePath, document->contents(), document->revision());
254 }
255 
unsavedFilesUpdatedFromCppEditorDocument(const QString & filePath)256 void BackendCommunicator::unsavedFilesUpdatedFromCppEditorDocument(const QString &filePath)
257 {
258     const CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
259     QTC_ASSERT(document, return);
260     unsavedFilesUpdated(filePath, document->contents(), document->revision());
261 }
262 
documentsChanged(const QString & filePath,const QByteArray & contents,uint documentRevision)263 void BackendCommunicator::documentsChanged(const QString &filePath,
264                                            const QByteArray &contents,
265                                            uint documentRevision)
266 {
267     const bool hasUnsavedContent = true;
268 
269     documentsChanged({{filePath,
270                        Utf8String::fromByteArray(contents),
271                        hasUnsavedContent,
272                        documentRevision}});
273 }
274 
unsavedFilesUpdated(const QString & filePath,const QByteArray & contents,uint documentRevision)275 void BackendCommunicator::unsavedFilesUpdated(const QString &filePath,
276                                               const QByteArray &contents,
277                                               uint documentRevision)
278 {
279     const bool hasUnsavedContent = true;
280 
281     // TODO: Send new only if changed
282     unsavedFilesUpdated({{filePath,
283                           Utf8String::fromByteArray(contents),
284                           hasUnsavedContent,
285                           documentRevision}});
286 }
287 
documentHasChanged(const QString & filePath,uint revision)288 static bool documentHasChanged(const QString &filePath, uint revision)
289 {
290     if (CppTools::CppEditorDocumentHandle *document = cppDocument(filePath))
291         return document->sendTracker().shouldSendRevision(revision);
292 
293     return true;
294 }
295 
documentsChangedWithRevisionCheck(const FileContainer & fileContainer)296 void BackendCommunicator::documentsChangedWithRevisionCheck(const FileContainer &fileContainer)
297 {
298     if (documentHasChanged(fileContainer.filePath, fileContainer.documentRevision)) {
299         documentsChanged({fileContainer});
300         setLastSentDocumentRevision(fileContainer.filePath,
301                                     fileContainer.documentRevision);
302     }
303 }
304 
requestAnnotations(const FileContainer & fileContainer)305 void BackendCommunicator::requestAnnotations(const FileContainer &fileContainer)
306 {
307     const RequestAnnotationsMessage message(fileContainer);
308     m_sender->requestAnnotations(message);
309 }
310 
requestReferences(const FileContainer & fileContainer,quint32 line,quint32 column,const CppTools::SemanticInfo::LocalUseMap & localUses)311 QFuture<CppTools::CursorInfo> BackendCommunicator::requestReferences(
312         const FileContainer &fileContainer,
313         quint32 line,
314         quint32 column,
315         const CppTools::SemanticInfo::LocalUseMap &localUses)
316 {
317     const RequestReferencesMessage message(fileContainer, line, column);
318     m_sender->requestReferences(message);
319 
320     return m_receiver.addExpectedReferencesMessage(message.ticketNumber, localUses);
321 }
322 
requestLocalReferences(const FileContainer & fileContainer,quint32 line,quint32 column)323 QFuture<CppTools::CursorInfo> BackendCommunicator::requestLocalReferences(
324         const FileContainer &fileContainer,
325         quint32 line,
326         quint32 column)
327 {
328     const RequestReferencesMessage message(fileContainer, line, column, true);
329     m_sender->requestReferences(message);
330 
331     return m_receiver.addExpectedReferencesMessage(message.ticketNumber);
332 }
333 
requestToolTip(const FileContainer & fileContainer,quint32 line,quint32 column)334 QFuture<CppTools::ToolTipInfo> BackendCommunicator::requestToolTip(
335         const FileContainer &fileContainer, quint32 line, quint32 column)
336 {
337     const RequestToolTipMessage message(fileContainer, line, column);
338     m_sender->requestToolTip(message);
339 
340     return m_receiver.addExpectedToolTipMessage(message.ticketNumber);
341 }
342 
requestFollowSymbol(const FileContainer & curFileContainer,quint32 line,quint32 column)343 QFuture<CppTools::SymbolInfo> BackendCommunicator::requestFollowSymbol(
344         const FileContainer &curFileContainer,
345         quint32 line,
346         quint32 column)
347 {
348     const RequestFollowSymbolMessage message(curFileContainer, line, column);
349     m_sender->requestFollowSymbol(message);
350 
351     return m_receiver.addExpectedRequestFollowSymbolMessage(message.ticketNumber);
352 }
353 
documentsChangedWithRevisionCheck(Core::IDocument * document)354 void BackendCommunicator::documentsChangedWithRevisionCheck(Core::IDocument *document)
355 {
356     const auto textDocument = qobject_cast<TextDocument*>(document);
357     const auto filePath = textDocument->filePath().toString();
358 
359     documentsChangedWithRevisionCheck(
360         FileContainer(filePath, {}, {}, textDocument->document()->revision()));
361 }
362 
updateChangeContentStartPosition(const QString & filePath,int position)363 void BackendCommunicator::updateChangeContentStartPosition(const QString &filePath, int position)
364 {
365     if (CppTools::CppEditorDocumentHandle *document = cppDocument(filePath))
366         document->sendTracker().applyContentChange(position);
367 }
368 
documentsChangedIfNotCurrentDocument(Core::IDocument * document)369 void BackendCommunicator::documentsChangedIfNotCurrentDocument(Core::IDocument *document)
370 {
371     QTC_ASSERT(document, return);
372     if (Core::EditorManager::currentDocument() != document)
373         documentsChanged(document);
374 }
375 
documentsChanged(Core::IDocument * document)376 void BackendCommunicator::documentsChanged(Core::IDocument *document)
377 {
378     documentsChangedFromCppEditorDocument(document->filePath().toString());
379 }
380 
unsavedFilesUpdated(Core::IDocument * document)381 void BackendCommunicator::unsavedFilesUpdated(Core::IDocument *document)
382 {
383     QTC_ASSERT(document, return);
384 
385      unsavedFilesUpdatedFromCppEditorDocument(document->filePath().toString());
386 }
387 
onConnectedToBackend()388 void BackendCommunicator::onConnectedToBackend()
389 {
390     m_backendStartTimeOut.stop();
391 
392     ++m_connectedCount;
393     if (m_connectedCount > 1)
394         logRestartedDueToUnexpectedFinish();
395 
396     m_receiver.reset();
397     m_sender.reset(new BackendSender(&m_connection));
398 
399     initializeBackendWithCurrentData();
400 }
401 
onEditorAboutToClose(Core::IEditor * editor)402 void BackendCommunicator::onEditorAboutToClose(Core::IEditor *editor)
403 {
404     if (auto *textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
405         m_receiver.deleteProcessorsOfEditorWidget(textEditor->editorWidget());
406 }
407 
setupDummySender()408 void BackendCommunicator::setupDummySender()
409 {
410     m_sender.reset(new DummyBackendSender);
411 }
412 
logExecutableDoesNotExist()413 void BackendCommunicator::logExecutableDoesNotExist()
414 {
415     const QString msg
416         = tr("Clang Code Model: Error: "
417              "The clangbackend executable \"%1\" does not exist.")
418                 .arg(QDir::toNativeSeparators(backendProcessPath()));
419 
420     logError(msg);
421 }
422 
logStartTimeOut()423 void BackendCommunicator::logStartTimeOut()
424 {
425     const QString msg
426         = tr("Clang Code Model: Error: "
427              "The clangbackend executable \"%1\" could not be started (timeout after %2ms).")
428                 .arg(QDir::toNativeSeparators(backendProcessPath()))
429                 .arg(backEndStartTimeOutInMs);
430 
431     logError(msg);
432 }
433 
logRestartedDueToUnexpectedFinish()434 void BackendCommunicator::logRestartedDueToUnexpectedFinish()
435 {
436     const QString msg
437         = tr("Clang Code Model: Error: "
438              "The clangbackend process has finished unexpectedly and was restarted.");
439 
440     logError(msg);
441 }
442 
logError(const QString & text)443 void BackendCommunicator::logError(const QString &text)
444 {
445     const QString textWithTimestamp = QDateTime::currentDateTime().toString(Qt::ISODate)
446             + ' ' + text;
447     Core::MessageManager::writeFlashing(textWithTimestamp);
448     qWarning("%s", qPrintable(textWithTimestamp));
449 }
450 
initializeBackendWithCurrentData()451 void BackendCommunicator::initializeBackendWithCurrentData()
452 {
453     unsavedFilesUpdatedForUiHeaders();
454     restoreCppEditorDocuments();
455     documentVisibilityChanged();
456 }
457 
documentsOpened(const FileContainers & fileContainers)458 void BackendCommunicator::documentsOpened(const FileContainers &fileContainers)
459 {
460     Utf8String currentDocument;
461     Utf8StringVector visibleDocuments;
462     if (!m_postponeBackendJobs) {
463         currentDocument = currentCppEditorDocumentFilePath();
464         visibleDocuments = visibleCppEditorDocumentsFilePaths();
465     }
466 
467     const DocumentsOpenedMessage message(fileContainers, currentDocument, visibleDocuments);
468     m_sender->documentsOpened(message);
469 }
470 
documentsChanged(const FileContainers & fileContainers)471 void BackendCommunicator::documentsChanged(const FileContainers &fileContainers)
472 {
473     const DocumentsChangedMessage message(fileContainers);
474     m_sender->documentsChanged(message);
475 }
476 
documentsClosed(const FileContainers & fileContainers)477 void BackendCommunicator::documentsClosed(const FileContainers &fileContainers)
478 {
479     const DocumentsClosedMessage message(fileContainers);
480     m_sender->documentsClosed(message);
481     documentVisibilityChanged(); // QTCREATORBUG-25193
482 }
483 
unsavedFilesUpdated(const FileContainers & fileContainers)484 void BackendCommunicator::unsavedFilesUpdated(const FileContainers &fileContainers)
485 {
486     const UnsavedFilesUpdatedMessage message(fileContainers);
487     m_sender->unsavedFilesUpdated(message);
488 }
489 
unsavedFilesRemoved(const FileContainers & fileContainers)490 void BackendCommunicator::unsavedFilesRemoved(const FileContainers &fileContainers)
491 {
492     const UnsavedFilesRemovedMessage message(fileContainers);
493     m_sender->unsavedFilesRemoved(message);
494 }
495 
requestCompletions(ClangCompletionAssistProcessor * assistProcessor,const QString & filePath,quint32 line,quint32 column,qint32 funcNameStartLine,qint32 funcNameStartColumn)496 void BackendCommunicator::requestCompletions(ClangCompletionAssistProcessor *assistProcessor,
497                                              const QString &filePath,
498                                              quint32 line,
499                                              quint32 column,
500                                              qint32 funcNameStartLine,
501                                              qint32 funcNameStartColumn)
502 {
503     const RequestCompletionsMessage message(filePath,
504                                             line,
505                                             column,
506                                             funcNameStartLine,
507                                             funcNameStartColumn);
508     m_sender->requestCompletions(message);
509     m_receiver.addExpectedCompletionsMessage(message.ticketNumber, assistProcessor);
510 }
511 
cancelCompletions(TextEditor::IAssistProcessor * processor)512 void BackendCommunicator::cancelCompletions(TextEditor::IAssistProcessor *processor)
513 {
514     m_receiver.cancelProcessor(processor);
515 }
516 
517 } // namespace Internal
518 } // namespace ClangCodeModel
519