1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 "builtineditordocumentparser.h"
27 #include "cppsourceprocessor.h"
28 
29 #include <projectexplorer/projectmacro.h>
30 #include <projectexplorer/projectexplorerconstants.h>
31 
32 #include <utils/algorithm.h>
33 #include <utils/qtcassert.h>
34 
35 using namespace CPlusPlus;
36 using namespace CppTools;
37 using namespace CppTools::Internal;
38 
overwrittenToolchainDefines(const ProjectPart & projectPart)39 static QByteArray overwrittenToolchainDefines(const ProjectPart &projectPart)
40 {
41     QByteArray defines;
42 
43     // MSVC's predefined macros like __FUNCSIG__ expand to itself.
44     // We can't parse this, so redefine to the empty string literal.
45     if (projectPart.toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) {
46         defines += "#define __FUNCSIG__ \"void __cdecl someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580(void)\"\n"
47                    "#define __FUNCDNAME__ \"?someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580@@YAXXZ\"\n"
48                    "#define __FUNCTION__ \"someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580\"\n";
49     }
50 
51     return defines;
52 }
53 
BuiltinEditorDocumentParser(const QString & filePath,int fileSizeLimitInMb)54 BuiltinEditorDocumentParser::BuiltinEditorDocumentParser(const QString &filePath,
55                                                          int fileSizeLimitInMb)
56     : BaseEditorDocumentParser(filePath)
57     , m_fileSizeLimitInMb(fileSizeLimitInMb)
58 {
59     qRegisterMetaType<CPlusPlus::Snapshot>("CPlusPlus::Snapshot");
60 }
61 
updateImpl(const QFutureInterface<void> & future,const UpdateParams & updateParams)62 void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface<void> &future,
63                                              const UpdateParams &updateParams)
64 {
65     if (filePath().isEmpty())
66         return;
67 
68     const Configuration baseConfig = configuration();
69     const bool releaseSourceAndAST_ = releaseSourceAndAST();
70 
71     State baseState = state();
72     ExtraState state = extraState();
73     WorkingCopy workingCopy = updateParams.workingCopy;
74 
75     bool invalidateSnapshot = false, invalidateConfig = false, editorDefinesChanged_ = false;
76 
77     CppModelManager *modelManager = CppModelManager::instance();
78     QByteArray configFile = modelManager->codeModelConfiguration();
79     ProjectExplorer::HeaderPaths headerPaths;
80     QStringList includedFiles;
81     QStringList precompiledHeaders;
82     QString projectConfigFile;
83     LanguageFeatures features = LanguageFeatures::defaultFeatures();
84 
85     baseState.projectPartInfo = determineProjectPart(filePath(),
86                                                     baseConfig.preferredProjectPartId,
87                                                     baseState.projectPartInfo,
88                                                     updateParams.activeProject,
89                                                     updateParams.languagePreference,
90                                                     updateParams.projectsUpdated);
91     emit projectPartInfoUpdated(baseState.projectPartInfo);
92 
93     if (state.forceSnapshotInvalidation) {
94         invalidateSnapshot = true;
95         state.forceSnapshotInvalidation = false;
96     }
97 
98     if (const ProjectPart::Ptr part = baseState.projectPartInfo.projectPart) {
99         configFile += ProjectExplorer::Macro::toByteArray(part->toolChainMacros);
100         configFile += overwrittenToolchainDefines(*part.data());
101         configFile += ProjectExplorer::Macro::toByteArray(part->projectMacros);
102         if (!part->projectConfigFile.isEmpty())
103             configFile += ProjectPart::readProjectConfigFile(part);
104         headerPaths = part->headerPaths;
105         projectConfigFile = part->projectConfigFile;
106         includedFiles = part->includedFiles;
107         if (baseConfig.usePrecompiledHeaders)
108             precompiledHeaders = part->precompiledHeaders;
109         features = part->languageFeatures;
110     }
111 
112     if (configFile != state.configFile) {
113         state.configFile = configFile;
114         invalidateSnapshot = true;
115         invalidateConfig = true;
116     }
117 
118     if (baseConfig.editorDefines != baseState.editorDefines) {
119         baseState.editorDefines = baseConfig.editorDefines;
120         invalidateSnapshot = true;
121         editorDefinesChanged_ = true;
122     }
123 
124     if (headerPaths != state.headerPaths) {
125         state.headerPaths = headerPaths;
126         invalidateSnapshot = true;
127     }
128 
129     if (projectConfigFile != state.projectConfigFile) {
130         state.projectConfigFile = projectConfigFile;
131         invalidateSnapshot = true;
132     }
133 
134     if (includedFiles != state.includedFiles) {
135         state.includedFiles = includedFiles;
136         invalidateSnapshot = true;
137     }
138 
139     if (precompiledHeaders != state.precompiledHeaders) {
140         state.precompiledHeaders = precompiledHeaders;
141         invalidateSnapshot = true;
142     }
143 
144     unsigned rev = 0;
145     if (Document::Ptr doc = state.snapshot.document(filePath()))
146         rev = doc->revision();
147     else
148         invalidateSnapshot = true;
149 
150     Snapshot globalSnapshot = modelManager->snapshot();
151 
152     if (invalidateSnapshot) {
153         state.snapshot = Snapshot();
154     } else {
155         // Remove changed files from the snapshot
156         QSet<Utils::FilePath> toRemove;
157         foreach (const Document::Ptr &doc, state.snapshot) {
158             const Utils::FilePath fileName = Utils::FilePath::fromString(doc->fileName());
159             if (workingCopy.contains(fileName)) {
160                 if (workingCopy.get(fileName).second != doc->editorRevision())
161                     addFileAndDependencies(&state.snapshot, &toRemove, fileName);
162                 continue;
163             }
164             Document::Ptr otherDoc = globalSnapshot.document(fileName);
165             if (!otherDoc.isNull() && otherDoc->revision() != doc->revision())
166                 addFileAndDependencies(&state.snapshot, &toRemove, fileName);
167         }
168 
169         if (!toRemove.isEmpty()) {
170             invalidateSnapshot = true;
171             foreach (const Utils::FilePath &fileName, toRemove)
172                 state.snapshot.remove(fileName);
173         }
174     }
175 
176     // Update the snapshot
177     if (invalidateSnapshot) {
178         const QString configurationFileName = CppModelManager::configurationFileName();
179         if (invalidateConfig)
180             state.snapshot.remove(configurationFileName);
181         if (!state.snapshot.contains(configurationFileName))
182             workingCopy.insert(configurationFileName, state.configFile);
183         state.snapshot.remove(filePath());
184 
185         static const QString editorDefinesFileName
186             = CppModelManager::editorConfigurationFileName();
187         if (editorDefinesChanged_) {
188             state.snapshot.remove(editorDefinesFileName);
189             workingCopy.insert(editorDefinesFileName, baseState.editorDefines);
190         }
191 
192         CppSourceProcessor sourceProcessor(state.snapshot, [&](const Document::Ptr &doc) {
193             const QString fileName = doc->fileName();
194             const bool isInEditor = fileName == filePath();
195             Document::Ptr otherDoc = modelManager->document(fileName);
196             unsigned newRev = otherDoc.isNull() ? 1U : otherDoc->revision() + 1;
197             if (isInEditor)
198                 newRev = qMax(rev + 1, newRev);
199             doc->setRevision(newRev);
200             modelManager->emitDocumentUpdated(doc);
201             if (releaseSourceAndAST_)
202                 doc->releaseSourceAndAST();
203         });
204         sourceProcessor.setFileSizeLimitInMb(m_fileSizeLimitInMb);
205         sourceProcessor.setCancelChecker([future]() {
206            return future.isCanceled();
207         });
208 
209         Snapshot globalSnapshot = modelManager->snapshot();
210         globalSnapshot.remove(filePath());
211         sourceProcessor.setGlobalSnapshot(globalSnapshot);
212         sourceProcessor.setWorkingCopy(workingCopy);
213         sourceProcessor.setHeaderPaths(state.headerPaths);
214         sourceProcessor.setLanguageFeatures(features);
215         sourceProcessor.run(configurationFileName);
216         if (baseConfig.usePrecompiledHeaders) {
217             foreach (const QString &precompiledHeader, state.precompiledHeaders)
218                 sourceProcessor.run(precompiledHeader);
219         }
220         if (!baseState.editorDefines.isEmpty())
221             sourceProcessor.run(editorDefinesFileName);
222         QStringList includedFiles = state.includedFiles;
223         if (baseConfig.usePrecompiledHeaders)
224             includedFiles << state.precompiledHeaders;
225         includedFiles.removeDuplicates();
226         sourceProcessor.run(filePath(), includedFiles);
227         state.snapshot = sourceProcessor.snapshot();
228         Snapshot newSnapshot = state.snapshot.simplified(state.snapshot.document(filePath()));
229         for (Snapshot::const_iterator i = state.snapshot.begin(), ei = state.snapshot.end(); i != ei; ++i) {
230             if (Client::isInjectedFile(i.key().toString()))
231                 newSnapshot.insert(i.value());
232         }
233         state.snapshot = newSnapshot;
234         state.snapshot.updateDependencyTable();
235     }
236 
237     setState(baseState);
238     setExtraState(state);
239 
240     if (invalidateSnapshot)
241         emit finished(state.snapshot.document(filePath()), state.snapshot);
242 }
243 
releaseResources()244 void BuiltinEditorDocumentParser::releaseResources()
245 {
246     ExtraState s = extraState();
247     s.snapshot = Snapshot();
248     s.forceSnapshotInvalidation = true;
249     setExtraState(s);
250 }
251 
document() const252 Document::Ptr BuiltinEditorDocumentParser::document() const
253 {
254     return extraState().snapshot.document(filePath());
255 }
256 
snapshot() const257 Snapshot BuiltinEditorDocumentParser::snapshot() const
258 {
259     return extraState().snapshot;
260 }
261 
headerPaths() const262 ProjectExplorer::HeaderPaths BuiltinEditorDocumentParser::headerPaths() const
263 {
264     return extraState().headerPaths;
265 }
266 
get(const QString & filePath)267 BuiltinEditorDocumentParser::Ptr BuiltinEditorDocumentParser::get(const QString &filePath)
268 {
269     if (BaseEditorDocumentParser::Ptr b = BaseEditorDocumentParser::get(filePath))
270         return b.objectCast<BuiltinEditorDocumentParser>();
271     return BuiltinEditorDocumentParser::Ptr();
272 }
273 
addFileAndDependencies(Snapshot * snapshot,QSet<Utils::FilePath> * toRemove,const Utils::FilePath & fileName) const274 void BuiltinEditorDocumentParser::addFileAndDependencies(Snapshot *snapshot,
275                                                          QSet<Utils::FilePath> *toRemove,
276                                                          const Utils::FilePath &fileName) const
277 {
278     QTC_ASSERT(snapshot, return);
279 
280     toRemove->insert(fileName);
281     if (fileName != Utils::FilePath::fromString(filePath())) {
282         Utils::FilePaths deps = snapshot->filesDependingOn(fileName);
283         toRemove->unite(Utils::toSet(deps));
284     }
285 }
286 
extraState() const287 BuiltinEditorDocumentParser::ExtraState BuiltinEditorDocumentParser::extraState() const
288 {
289     QMutexLocker locker(&m_stateAndConfigurationMutex);
290     return m_extraState;
291 }
292 
setExtraState(const ExtraState & extraState)293 void BuiltinEditorDocumentParser::setExtraState(const ExtraState &extraState)
294 {
295     QMutexLocker locker(&m_stateAndConfigurationMutex);
296     m_extraState = extraState;
297 }
298 
releaseSourceAndAST() const299 bool BuiltinEditorDocumentParser::releaseSourceAndAST() const
300 {
301     QMutexLocker locker(&m_stateAndConfigurationMutex);
302     return m_releaseSourceAndAST;
303 }
304 
setReleaseSourceAndAST(bool release)305 void BuiltinEditorDocumentParser::setReleaseSourceAndAST(bool release)
306 {
307     QMutexLocker locker(&m_stateAndConfigurationMutex);
308     m_releaseSourceAndAST = release;
309 }
310