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 "qmljsdocument.h"
27 #include "qmljsbind.h"
28 #include "qmljsconstants.h"
29 #include "qmljsimportdependencies.h"
30 #include <qmljs/parser/qmljslexer_p.h>
31 #include <qmljs/parser/qmljsparser_p.h>
32 
33 #include <utils/qtcassert.h>
34 
35 #include <QCryptographicHash>
36 #include <QDir>
37 #include <QFileInfo>
38 #include <QRegExp>
39 
40 #include <algorithm>
41 
42 using namespace QmlJS;
43 using namespace QmlJS::AST;
44 
45 /*!
46     \class QmlJS::Document
47     \brief The Document class creates a QML or JavaScript document.
48     \sa Snapshot
49 
50     Documents are usually created by the ModelManagerInterface
51     and stored in a Snapshot. They allow access to data such as
52     the file path, source code, abstract syntax tree and the Bind
53     instance for the document.
54 
55     To make sure unused and outdated documents are removed correctly, Document
56     instances are usually accessed through a shared pointer, see Document::Ptr.
57 
58     Documents in a Snapshot are immutable: They, or anything reachable through them,
59     must not be changed. This allows Documents to be shared freely among threads
60     without extra synchronization.
61 */
62 
63 /*!
64     \class QmlJS::LibraryInfo
65     \brief The LibraryInfo class creates a QML library.
66     \sa Snapshot
67 
68     A LibraryInfo is created when the ModelManagerInterface finds
69     a QML library and parses the qmldir file. The instance holds information about
70     which Components the library provides and which plugins to load.
71 
72     The ModelManager will try to extract detailed information about the types
73     defined in the plugins this library loads. Once it is done, the data will
74     be available through the metaObjects() function.
75 */
76 
77 /*!
78     \class QmlJS::Snapshot
79     \brief The Snapshot class holds and offers access to a set of
80     Document::Ptr and LibraryInfo instances.
81     \sa Document LibraryInfo
82 
83     Usually Snapshots are copies of the snapshot maintained and updated by the
84     ModelManagerInterface that updates its instance as parsing
85     threads finish and new information becomes available.
86 */
87 
Document(const QString & fileName,Dialect language)88 Document::Document(const QString &fileName, Dialect language)
89     : _engine(0)
90     , _ast(0)
91     , _bind(0)
92     , _fileName(QDir::cleanPath(fileName))
93     , _editorRevision(0)
94     , _language(language)
95     , _parsedCorrectly(false)
96 {
97     QFileInfo fileInfo(fileName);
98     _path = QDir::cleanPath(fileInfo.absolutePath());
99 
100     if (language.isQmlLikeLanguage()) {
101         _componentName = fileInfo.baseName();
102 
103         if (! _componentName.isEmpty()) {
104             // ### TODO: check the component name.
105 
106             if (! _componentName.at(0).isUpper())
107                 _componentName.clear();
108         }
109     }
110 }
111 
~Document()112 Document::~Document()
113 {
114     if (_bind)
115         delete _bind;
116 
117     if (_engine)
118         delete _engine;
119 }
120 
create(const QString & fileName,Dialect language)121 Document::MutablePtr Document::create(const QString &fileName, Dialect language)
122 {
123     Document::MutablePtr doc(new Document(fileName, language));
124     doc->_ptr = doc;
125     return doc;
126 }
127 
ptr() const128 Document::Ptr Document::ptr() const
129 {
130     return _ptr.toStrongRef();
131 }
132 
isQmlDocument() const133 bool Document::isQmlDocument() const
134 {
135     return _language.isQmlLikeLanguage();
136 }
137 
language() const138 Dialect Document::language() const
139 {
140     return _language;
141 }
142 
setLanguage(Dialect l)143 void Document::setLanguage(Dialect l)
144 {
145     _language = l;
146 }
147 
importId() const148 QString Document::importId() const
149 {
150     return _fileName;
151 }
152 
fingerprint() const153 QByteArray Document::fingerprint() const
154 {
155     return _fingerprint;
156 }
157 
qmlProgram() const158 AST::UiProgram *Document::qmlProgram() const
159 {
160     return cast<UiProgram *>(_ast);
161 }
162 
jsProgram() const163 AST::Program *Document::jsProgram() const
164 {
165     return cast<Program *>(_ast);
166 }
167 
expression() const168 AST::ExpressionNode *Document::expression() const
169 {
170     if (_ast)
171         return _ast->expressionCast();
172 
173     return 0;
174 }
175 
ast() const176 AST::Node *Document::ast() const
177 {
178     return _ast;
179 }
180 
engine() const181 const QmlJS::Engine *Document::engine() const
182 {
183     return _engine;
184 }
185 
diagnosticMessages() const186 QList<DiagnosticMessage> Document::diagnosticMessages() const
187 {
188     return _diagnosticMessages;
189 }
190 
source() const191 QString Document::source() const
192 {
193     return _source;
194 }
195 
setSource(const QString & source)196 void Document::setSource(const QString &source)
197 {
198     _source = source;
199     QCryptographicHash sha(QCryptographicHash::Sha1);
200     sha.addData(source.toUtf8());
201     _fingerprint = sha.result();
202 }
203 
editorRevision() const204 int Document::editorRevision() const
205 {
206     return _editorRevision;
207 }
208 
setEditorRevision(int revision)209 void Document::setEditorRevision(int revision)
210 {
211     _editorRevision = revision;
212 }
213 
fileName() const214 QString Document::fileName() const
215 {
216     return _fileName;
217 
218 }
219 
path() const220 QString Document::path() const
221 {
222     return _path;
223 }
224 
componentName() const225 QString Document::componentName() const
226 {
227     return _componentName;
228 }
229 
230 namespace {
231 class CollectDirectives : public Directives
232 {
addLocation(int line,int column)233     void addLocation(int line, int column) {
234         const SourceLocation loc = SourceLocation(
235                     0,  // placeholder
236                     0,  // placeholder
237                     static_cast<quint32>(line),
238                     static_cast<quint32>(column));
239         _locations += loc;
240     }
241 
242     QList<SourceLocation> _locations;
243 
244 public:
CollectDirectives(const QString & documentPath)245     CollectDirectives(const QString &documentPath)
246         : documentPath(documentPath)
247         , isLibrary(false)
248 
249     {}
250 
pragmaLibrary(int line,int column)251     void pragmaLibrary(int line, int column) override
252     {
253         isLibrary = true;
254         addLocation(line, column);
255     }
256 
importFile(const QString & jsfile,const QString & module,int line,int column)257     void importFile(const QString &jsfile, const QString &module,
258                     int line, int column) override
259     {
260         imports += ImportInfo::pathImport(
261                     documentPath, jsfile, LanguageUtils::ComponentVersion(), module);
262         addLocation(line, column);
263     }
264 
importModule(const QString & uri,const QString & version,const QString & module,int line,int column)265     void importModule(const QString &uri, const QString &version, const QString &module,
266                       int line, int column) override
267     {
268         imports += ImportInfo::moduleImport(uri, LanguageUtils::ComponentVersion(version), module);
269         addLocation(line, column);
270     }
271 
locations()272     virtual QList<SourceLocation> locations() { return _locations; }
273 
274     const QString documentPath;
275     bool isLibrary;
276     QList<ImportInfo> imports;
277 };
278 
279 } // anonymous namespace
280 
281 
jsDirectives() const282 QList<SourceLocation> Document::jsDirectives() const
283 {
284     return _jsdirectives;
285 }
286 
parse_helper(int startToken)287 bool Document::parse_helper(int startToken)
288 {
289     Q_ASSERT(! _engine);
290     Q_ASSERT(! _ast);
291     Q_ASSERT(! _bind);
292 
293     _engine = new Engine();
294 
295     Lexer lexer(_engine);
296     Parser parser(_engine);
297 
298     QString source = _source;
299     lexer.setCode(source, /*line = */ 1, /*qmlMode = */_language.isQmlLikeLanguage());
300 
301     CollectDirectives directives = CollectDirectives(path());
302     _engine->setDirectives(&directives);
303 
304     switch (startToken) {
305     case QmlJSGrammar::T_FEED_UI_PROGRAM:
306         _parsedCorrectly = parser.parse();
307         break;
308     case QmlJSGrammar::T_FEED_JS_PROGRAM:
309         _parsedCorrectly = parser.parseProgram();
310         for (const auto &d: directives.locations()) {
311             _jsdirectives << d;
312         }
313         break;
314     case QmlJSGrammar::T_FEED_JS_EXPRESSION:
315         _parsedCorrectly = parser.parseExpression();
316         break;
317     default:
318         Q_ASSERT(0);
319     }
320 
321     _ast = parser.rootNode();
322     _diagnosticMessages = parser.diagnosticMessages();
323 
324     _bind = new Bind(this, &_diagnosticMessages, directives.isLibrary, directives.imports);
325 
326     return _parsedCorrectly;
327 }
328 
parse()329 bool Document::parse()
330 {
331     if (isQmlDocument())
332         return parseQml();
333 
334     return parseJavaScript();
335 }
336 
parseQml()337 bool Document::parseQml()
338 {
339     return parse_helper(QmlJSGrammar::T_FEED_UI_PROGRAM);
340 }
341 
parseJavaScript()342 bool Document::parseJavaScript()
343 {
344     return parse_helper(QmlJSGrammar::T_FEED_JS_PROGRAM);
345 }
346 
parseExpression()347 bool Document::parseExpression()
348 {
349     return parse_helper(QmlJSGrammar::T_FEED_JS_EXPRESSION);
350 }
351 
bind() const352 Bind *Document::bind() const
353 {
354     return _bind;
355 }
356 
LibraryInfo(Status status)357 LibraryInfo::LibraryInfo(Status status)
358     : _status(status)
359     , _dumpStatus(NoTypeInfo)
360 {
361     updateFingerprint();
362 }
363 
LibraryInfo(const QmlDirParser & parser,const QByteArray & fingerprint)364 LibraryInfo::LibraryInfo(const QmlDirParser &parser, const QByteArray &fingerprint)
365     : _status(Found)
366     , _components(parser.components().values())
367     , _plugins(parser.plugins())
368     , _typeinfos(parser.typeInfos())
369     , _fingerprint(fingerprint)
370     , _dumpStatus(NoTypeInfo)
371 {
372     if (_fingerprint.isEmpty())
373         updateFingerprint();
374 }
375 
~LibraryInfo()376 LibraryInfo::~LibraryInfo()
377 {
378 }
379 
calculateFingerprint() const380 QByteArray LibraryInfo::calculateFingerprint() const
381 {
382     QCryptographicHash hash(QCryptographicHash::Sha1);
383     hash.addData(reinterpret_cast<const char *>(&_status), sizeof(_status));
384     int len = _components.size();
385     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
386     foreach (const QmlDirParser::Component &component, _components) {
387         len = component.fileName.size();
388         hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
389         hash.addData(reinterpret_cast<const char *>(component.fileName.constData()), len * sizeof(QChar));
390         hash.addData(reinterpret_cast<const char *>(&component.majorVersion), sizeof(component.majorVersion));
391         hash.addData(reinterpret_cast<const char *>(&component.minorVersion), sizeof(component.minorVersion));
392         len = component.typeName.size();
393         hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
394         hash.addData(reinterpret_cast<const char *>(component.typeName.constData()), component.typeName.size() * sizeof(QChar));
395         int flags = (component.singleton ?  (1 << 0) : 0) + (component.internal ? (1 << 1) : 0);
396         hash.addData(reinterpret_cast<const char *>(&flags), sizeof(flags));
397     }
398     len = _plugins.size();
399     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
400     foreach (const QmlDirParser::Plugin &plugin, _plugins) {
401         len = plugin.path.size();
402         hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
403         hash.addData(reinterpret_cast<const char *>(plugin.path.constData()), len * sizeof(QChar));
404         len = plugin.name.size();
405         hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
406         hash.addData(reinterpret_cast<const char *>(plugin.name.constData()), len * sizeof(QChar));
407     }
408     len = _typeinfos.size();
409     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
410     foreach (const QmlDirParser::TypeInfo &typeinfo, _typeinfos) {
411         len = typeinfo.fileName.size();
412         hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
413         hash.addData(reinterpret_cast<const char *>(typeinfo.fileName.constData()), len * sizeof(QChar));
414     }
415     len = _metaObjects.size();
416     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
417     QList<QByteArray> metaFingerprints;
418     foreach (const LanguageUtils::FakeMetaObject::ConstPtr &metaObject, _metaObjects)
419         metaFingerprints.append(metaObject->fingerprint());
420     std::sort(metaFingerprints.begin(), metaFingerprints.end());
421     foreach (const QByteArray &fp, metaFingerprints)
422         hash.addData(fp);
423     hash.addData(reinterpret_cast<const char *>(&_dumpStatus), sizeof(_dumpStatus));
424     len = _dumpError.size(); // localization dependent (avoid?)
425     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
426     hash.addData(reinterpret_cast<const char *>(_dumpError.constData()), len * sizeof(QChar));
427 
428     len = _moduleApis.size();
429     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
430     foreach (const ModuleApiInfo &moduleInfo, _moduleApis)
431         moduleInfo.addToHash(hash); // make it order independent?
432 
433     QByteArray res(hash.result());
434     res.append('L');
435     return res;
436 }
437 
updateFingerprint()438 void LibraryInfo::updateFingerprint()
439 {
440     _fingerprint = calculateFingerprint();
441 }
442 
Snapshot()443 Snapshot::Snapshot()
444 {
445 }
446 
~Snapshot()447 Snapshot::~Snapshot()
448 {
449 }
450 
Snapshot(const Snapshot & o)451 Snapshot::Snapshot(const Snapshot &o)
452     : _documents(o._documents),
453       _documentsByPath(o._documentsByPath),
454       _libraries(o._libraries),
455       _dependencies(o._dependencies)
456 {
457 }
458 
insert(const Document::Ptr & document,bool allowInvalid)459 void Snapshot::insert(const Document::Ptr &document, bool allowInvalid)
460 {
461     if (document && (allowInvalid || document->qmlProgram() || document->jsProgram())) {
462         const QString fileName = document->fileName();
463         const QString path = document->path();
464         remove(fileName);
465         _documentsByPath[path].append(document);
466         _documents.insert(fileName, document);
467         CoreImport cImport;
468         cImport.importId = document->importId();
469         cImport.language = document->language();
470         cImport.possibleExports << Export(ImportKey(ImportType::File, fileName),
471                                           QString(), true, QFileInfo(fileName).baseName());
472         cImport.fingerprint = document->fingerprint();
473         _dependencies.addCoreImport(cImport);
474     }
475 }
476 
insertLibraryInfo(const QString & path,const LibraryInfo & info)477 void Snapshot::insertLibraryInfo(const QString &path, const LibraryInfo &info)
478 {
479     QTC_CHECK(!path.isEmpty());
480     QTC_CHECK(info.fingerprint() == info.calculateFingerprint());
481     _libraries.insert(QDir::cleanPath(path), info);
482     if (!info.wasFound()) return;
483     CoreImport cImport;
484     cImport.importId = path;
485     cImport.language = Dialect::AnyLanguage;
486     QSet<ImportKey> packages;
487     foreach (const ModuleApiInfo &moduleInfo, info.moduleApis()) {
488         ImportKey iKey(ImportType::Library, moduleInfo.uri, moduleInfo.version.majorVersion(),
489                        moduleInfo.version.minorVersion());
490         packages.insert(iKey);
491     }
492     foreach (const LanguageUtils::FakeMetaObject::ConstPtr &metaO, info.metaObjects()) {
493         foreach (const LanguageUtils::FakeMetaObject::Export &e, metaO->exports()) {
494             ImportKey iKey(ImportType::Library, e.package, e.version.majorVersion(),
495                            e.version.minorVersion());
496             packages.insert(iKey);
497         }
498     }
499 
500     QStringList splitPath = path.split(QLatin1Char('/'));
501     QRegExp vNr(QLatin1String("^(.+)\\.([0-9]+)(?:\\.([0-9]+))?$"));
502     QRegExp safeName(QLatin1String("^[a-zA-Z_][[a-zA-Z0-9_]*$"));
503     foreach (const ImportKey &importKey, packages) {
504         if (importKey.splitPath.size() == 1 && importKey.splitPath.at(0).isEmpty() && splitPath.length() > 0) {
505             // relocatable
506             QStringList myPath = splitPath;
507             if (vNr.indexIn(myPath.last()) == 0)
508                 myPath.last() = vNr.cap(1);
509             for (int iPath = myPath.size(); iPath != 1; ) {
510                 --iPath;
511                 if (safeName.indexIn(myPath.at(iPath)) != 0)
512                     break;
513                 ImportKey iKey(ImportType::Library, QStringList(myPath.mid(iPath)).join(QLatin1Char('.')),
514                                importKey.majorVersion, importKey.minorVersion);
515                 cImport.possibleExports.append(Export(iKey, (iPath == 1) ? QLatin1String("/") :
516                      QStringList(myPath.mid(0, iPath)).join(QLatin1Char('/')), true));
517             }
518         } else {
519             QString requiredPath = QStringList(splitPath.mid(0, splitPath.size() - importKey.splitPath.size()))
520                     .join(QLatin1String("/"));
521             cImport.possibleExports << Export(importKey, requiredPath, true);
522         }
523     }
524     if (cImport.possibleExports.isEmpty() && splitPath.size() > 0) {
525         QRegExp vNr(QLatin1String("^(.+)\\.([0-9]+)(?:\\.([0-9]+))?$"));
526         QRegExp safeName(QLatin1String("^[a-zA-Z_][[a-zA-Z0-9_]*$"));
527         int majorVersion = LanguageUtils::ComponentVersion::NoVersion;
528         int minorVersion = LanguageUtils::ComponentVersion::NoVersion;
529 
530         foreach (const QmlDirParser::Component &component, info.components()) {
531             if (component.majorVersion > majorVersion)
532                 majorVersion = component.majorVersion;
533             if (component.minorVersion > minorVersion)
534                 minorVersion = component.minorVersion;
535         }
536 
537         if (vNr.indexIn(splitPath.last()) == 0) {
538             splitPath.last() = vNr.cap(1);
539             bool ok;
540             majorVersion = vNr.cap(2).toInt(&ok);
541             if (!ok)
542                 majorVersion = LanguageUtils::ComponentVersion::NoVersion;
543             minorVersion = vNr.cap(3).toInt(&ok);
544             if (vNr.cap(3).isEmpty() || !ok)
545                 minorVersion = LanguageUtils::ComponentVersion::NoVersion;
546         }
547 
548         for (int iPath = splitPath.size(); iPath != 1; ) {
549             --iPath;
550             if (safeName.indexIn(splitPath.at(iPath)) != 0)
551                 break;
552             ImportKey iKey(ImportType::Library, QStringList(splitPath.mid(iPath)).join(QLatin1Char('.')),
553                            majorVersion, minorVersion);
554             cImport.possibleExports.append(Export(iKey, (iPath == 1) ? QLatin1String("/") :
555                 QStringList(splitPath.mid(0, iPath)).join(QLatin1Char('/')), true));
556         }
557     }
558     foreach (const QmlDirParser::Component &component, info.components()) {
559         foreach (const Export &e, cImport.possibleExports)
560             _dependencies.addExport(component.fileName, e.exportName, e.pathRequired, e.typeName);
561     }
562 
563     cImport.fingerprint = info.fingerprint();
564     _dependencies.addCoreImport(cImport);
565 }
566 
remove(const QString & fileName)567 void Snapshot::remove(const QString &fileName)
568 {
569     Document::Ptr doc = _documents.value(fileName);
570     if (!doc.isNull()) {
571         const QString &path = doc->path();
572 
573         QList<Document::Ptr> docs = _documentsByPath.value(path);
574         docs.removeAll(doc);
575         _documentsByPath[path] = docs;
576 
577         _documents.remove(fileName);
578     }
579 }
580 
importDependencies() const581 const QmlJS::ImportDependencies *Snapshot::importDependencies() const
582 {
583     return &_dependencies;
584 }
585 
importDependencies()586 QmlJS::ImportDependencies *Snapshot::importDependencies()
587 {
588     return &_dependencies;
589 }
590 
documentFromSource(const QString & code,const QString & fileName,Dialect language) const591 Document::MutablePtr Snapshot::documentFromSource(
592         const QString &code, const QString &fileName,
593         Dialect language) const
594 {
595     Document::MutablePtr newDoc = Document::create(fileName, language);
596 
597     if (Document::Ptr thisDocument = document(fileName))
598         newDoc->_editorRevision = thisDocument->_editorRevision;
599 
600     newDoc->setSource(code);
601     return newDoc;
602 }
603 
document(const QString & fileName) const604 Document::Ptr Snapshot::document(const QString &fileName) const
605 {
606     return _documents.value(QDir::cleanPath(fileName));
607 }
608 
documentsInDirectory(const QString & path) const609 QList<Document::Ptr> Snapshot::documentsInDirectory(const QString &path) const
610 {
611     return _documentsByPath.value(QDir::cleanPath(path));
612 }
613 
libraryInfo(const QString & path) const614 LibraryInfo Snapshot::libraryInfo(const QString &path) const
615 {
616     return _libraries.value(QDir::cleanPath(path));
617 }
618 
619 
addToHash(QCryptographicHash & hash) const620 void ModuleApiInfo::addToHash(QCryptographicHash &hash) const
621 {
622     int len = uri.length();
623     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
624     hash.addData(reinterpret_cast<const char *>(uri.constData()), len * sizeof(QChar));
625     version.addToHash(hash);
626     len = cppName.length();
627     hash.addData(reinterpret_cast<const char *>(&len), sizeof(len));
628     hash.addData(reinterpret_cast<const char *>(cppName.constData()), len * sizeof(QChar));
629 }
630