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 "qmljsfindexportedcpptypes.h"
27 
28 #include <qmljs/qmljsinterpreter.h>
29 #include <qmljs/qmljsdocument.h>
30 #include <cplusplus/Overview.h>
31 #include <cplusplus/TypeOfExpression.h>
32 #include <cplusplus/cppmodelmanagerbase.h>
33 #include <cplusplus/CppDocument.h>
34 #include <utils/qtcassert.h>
35 
36 #include <QList>
37 #include <QRegularExpression>
38 
39 //using namespace QmlJS;
40 
41 namespace {
42 using namespace CPlusPlus;
43 
44 class ExportedQmlType {
45 public:
46     QString packageName;
47     QString typeName;
48     LanguageUtils::ComponentVersion version;
49     Scope *scope;
50     QString typeExpression;
51     QString url;
52     bool isCreatable;
53     bool isSingleton;
54 };
55 
56 class ContextProperty {
57 public:
58     QString name;
59     QString expression;
60     int line, column;
61 };
62 
63 class FindExportsVisitor : protected ASTVisitor
64 {
65     CPlusPlus::Document::Ptr _doc;
66     QList<ExportedQmlType> _exportedTypes;
67     QList<ContextProperty> _contextProperties;
68     CompoundStatementAST *_compound;
69     ASTMatcher _matcher;
70     ASTPatternBuilder _builder;
71     Overview _overview;
72     QList<CPlusPlus::Document::DiagnosticMessage> _messages;
73 
74 public:
75     enum RegistrationFunction {
76         InvalidRegistrationFunction,
77         // template<typename T> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);
78         // or template<typename T, int metaObjectRevision> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);
79         QmlRegisterType4,
80         // int qmlRegisterType(const QUrl & url, const char * uri, int versionMajor, int versionMinor, const char * qmlName)
81         QmlRegisterType5,
82         // template<typename T> inline auto qmlRegisterSingletonInstance(const char *uri, int versionMajor, int versionMinor, const char *typeName, T *cppObject)
83         QmlRegisterSingletonInstance,
84         // template<typename T> int qmlRegisterSingletonType(const char * uri, int versionMajor, int versionMinor, const char * typeName, QObject *(* ) ( QQmlEngine *, QJSEngine * ) callback)
85         QmlRegisterSingletonTypeCallback1,
86         // int qmlRegisterSingletonType(const char * uri, int versionMajor, int versionMinor, const char * typeName, QJSValue(* ) ( QQmlEngine *, QJSEngine * ) callback)
87         QmlRegisterSingletonTypeCallback2,
88         // int qmlRegisterSingletonType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName)
89         QmlRegisterSingletonTypeUrl,
90         // template<typename T> int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString& reason)
91         // or template<typename T, int metaObjectRevision> int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString& reason)
92         QmlRegisterUncreatableType,
93         // int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString &reason)
94         QmlRegisterUncreatableMetaObject
95     };
96 
FindExportsVisitor(CPlusPlus::Document::Ptr doc)97     FindExportsVisitor(CPlusPlus::Document::Ptr doc)
98         : ASTVisitor(doc->translationUnit())
99         , _doc(doc)
100         , _compound(nullptr)
101     {}
102 
operator ()()103     void operator()()
104     {
105         _exportedTypes.clear();
106         _contextProperties.clear();
107         accept(translationUnit()->ast());
108     }
109 
messages() const110     QList<CPlusPlus::Document::DiagnosticMessage> messages() const
111     {
112         return _messages;
113     }
114 
exportedTypes() const115     QList<ExportedQmlType> exportedTypes() const
116     {
117         return _exportedTypes;
118     }
119 
contextProperties() const120     QList<ContextProperty> contextProperties() const
121     {
122         return _contextProperties;
123     }
124 
125 protected:
visit(CompoundStatementAST * ast)126     bool visit(CompoundStatementAST *ast) override
127     {
128         CompoundStatementAST *old = _compound;
129         _compound = ast;
130         accept(ast->statement_list);
131         _compound = old;
132         return false;
133     }
134 
visit(CallAST * ast)135     bool visit(CallAST *ast) override
136     {
137         if (checkForQmlRegisterType(ast))
138             return false;
139         checkForSetContextProperty(ast);
140         return false;
141     }
142 
checkForQmlRegisterType(CallAST * ast)143     bool checkForQmlRegisterType(CallAST *ast)
144     {
145         IdExpressionAST *idExp = ast->base_expression->asIdExpression();
146         if (!idExp || !idExp->name)
147             return false;
148         RegistrationFunction registrationFunction = InvalidRegistrationFunction;
149         TypeIdAST *typeId = nullptr;
150         if (TemplateIdAST *templateId = idExp->name->asTemplateId()) {
151             if (!templateId->identifier_token)
152                 return false;
153 
154             // check the name
155             const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token);
156             if (!templateIdentifier)
157                 return false;
158             const QByteArray callName(templateIdentifier->chars());
159             if (callName == "qmlRegisterType")
160                 registrationFunction = QmlRegisterType4;
161             else if (callName == "qmlRegisterSingletonInstance")
162                 registrationFunction = QmlRegisterSingletonInstance;
163             else if (callName == "qmlRegisterSingletonType")
164                 registrationFunction = QmlRegisterSingletonTypeCallback1;
165             else if (callName == "qmlRegisterUncreatableType")
166                 registrationFunction = QmlRegisterUncreatableType;
167             else
168                 return false;
169 
170             // check that there is a typeId
171             if (!templateId->template_argument_list || !templateId->template_argument_list->value)
172                 return false;
173             // sometimes there can be a second argument, the metaRevisionNumber
174             if (templateId->template_argument_list->next) {
175                 if (!templateId->template_argument_list->next->value ||
176                         templateId->template_argument_list->next->next)
177                     return false;
178                 // should just check for a generic ExpressionAST?
179                 NumericLiteralAST *metaRevision =
180                         templateId->template_argument_list->next->value->asNumericLiteral();
181                 if (!metaRevision)
182                     return false;
183             }
184 
185             typeId = templateId->template_argument_list->value->asTypeId();
186             if (!typeId)
187                 return false;
188         } else if (idExp->name) {
189             QByteArray fName;
190             if (SimpleNameAST *simpleName = idExp->name->asSimpleName()) {
191                 if (const Identifier *id = translationUnit()->identifier(simpleName->identifier_token))
192                     fName = QByteArray(id->chars(), id->size());
193             }
194             if (fName == "qmlRegisterType")
195                 registrationFunction = QmlRegisterType5;
196             else if (fName == "qmlRegisterSingletonInstance") // when called without explicit template parameter
197                 registrationFunction = QmlRegisterSingletonInstance;
198             else if (fName == "qmlRegisterSingletonType")
199                 registrationFunction = QmlRegisterSingletonTypeCallback2;
200             else if (fName == "qmlRegisterUncreatableMetaObject")
201                 registrationFunction = QmlRegisterUncreatableMetaObject;
202             else
203                 return false;
204         } else {
205             return false;
206         }
207 
208         int argCount = 0;
209         for (const ExpressionListAST *list = ast->expression_list; list && list->value;
210              list = list->next) {
211             ++argCount;
212         }
213 
214         // must have at least four arguments
215         if (argCount < 4)
216             return false;
217         switch (registrationFunction) {
218         case InvalidRegistrationFunction:
219             return false;
220         case QmlRegisterType4:
221             break;
222         case QmlRegisterType5:
223         case QmlRegisterSingletonInstance:
224         case QmlRegisterSingletonTypeCallback1:
225         case QmlRegisterSingletonTypeCallback2:
226         case QmlRegisterSingletonTypeUrl:
227         case QmlRegisterUncreatableType:
228             if (argCount != 5)
229                 return false;
230             break;
231         case QmlRegisterUncreatableMetaObject:
232             if (argCount != 6)
233                 return false;
234         }
235         ExpressionAST *uriExp = nullptr;
236         ExpressionAST *majorVersionExp = nullptr;
237         ExpressionAST *minorVersionExp = nullptr;
238         ExpressionAST *nameExp = nullptr;
239         if (registrationFunction == QmlRegisterType5) {
240             uriExp = ast->expression_list->next->value;
241             majorVersionExp = ast->expression_list->next->next->value;
242             minorVersionExp = ast->expression_list->next->next->next->value;
243             nameExp = ast->expression_list->next->next->next->next->value;
244         } else if (registrationFunction == QmlRegisterSingletonTypeCallback2
245                    && (!ast->expression_list->next->value->asNumericLiteral()
246                        || ast->expression_list->next->next->next->value->asNumericLiteral())) {
247             // discriminate between QmlRegisterSingletonTypeCallback2 and QmlRegisterSingletonTypeUrl,
248             // this is very rough check, improve?
249             registrationFunction = QmlRegisterSingletonTypeUrl;
250             uriExp = ast->expression_list->next->value;
251             majorVersionExp = ast->expression_list->next->next->value;
252             minorVersionExp = ast->expression_list->next->next->next->value;
253             nameExp = ast->expression_list->next->next->next->next->value;
254         } else if (registrationFunction == QmlRegisterUncreatableMetaObject) {
255             uriExp = ast->expression_list->next->value;
256             majorVersionExp = ast->expression_list->next->next->value;
257             minorVersionExp = ast->expression_list->next->next->next->value;
258             nameExp = ast->expression_list->next->next->next->next->value;
259         } else {
260             uriExp = ast->expression_list->value;
261             majorVersionExp = ast->expression_list->next->value;
262             minorVersionExp = ast->expression_list->next->next->value;
263             nameExp = ast->expression_list->next->next->next->value;
264         }
265         const StringLiteral *nameLit = nullptr;
266         if (StringLiteralAST *nameAst = skipStringCall(nameExp)->asStringLiteral())
267             nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
268         if (!nameLit) {
269             int line, column;
270             translationUnit()->getTokenStartPosition(nameExp->firstToken(), &line, &column);
271             _messages += Document::DiagnosticMessage(
272                         Document::DiagnosticMessage::Warning,
273                         _doc->fileName(),
274                         line, column,
275                         QmlJS::FindExportedCppTypes::tr(
276                             "The type will only be available in the QML editors when the type name is a string literal"));
277             return false;
278         }
279 
280         // if the uri is a string literal, things are easy
281         QString packageName;
282         if (StringLiteralAST *packageAst = skipStringCall(uriExp)->asStringLiteral()) {
283             const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token);
284             packageName = QString::fromUtf8(packageLit->chars(), packageLit->size());
285         }
286         // as a special case, allow an identifier package argument if there's a
287         // Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri"));
288         // in the enclosing compound statement
289         QString uriNameString;
290         if (IdExpressionAST *uriName = uriExp->asIdExpression())
291             uriNameString = stringOf(uriName);
292         if (packageName.isEmpty() && !uriNameString.isEmpty() && _compound) {
293             for (StatementListAST *it = _compound->statement_list; it; it = it->next) {
294                 StatementAST *stmt = it->value;
295 
296                 packageName = nameOfUriAssert(stmt, uriNameString);
297                 if (!packageName.isEmpty())
298                     break;
299             }
300         }
301         if (packageName.isEmpty() && _compound) {
302             // check the comments in _compound for annotations
303             const QRegularExpression uriAnnotation(QLatin1String("@uri\\s*([\\w\\.]*)"));
304 
305             // scan every comment between the pipes in
306             // {|
307             //    // comment
308             //    othercode;
309             //   |qmlRegisterType<Foo>(...);
310             const Token begin = _doc->translationUnit()->tokenAt(_compound->firstToken());
311             const Token end = _doc->translationUnit()->tokenAt(ast->firstToken());
312 
313             // go through comments backwards to find the annotation closest to the call
314             for (int i = _doc->translationUnit()->commentCount(); i-- > 0; ) {
315                 const Token commentToken = _doc->translationUnit()->commentAt(i);
316                 if (commentToken.utf16charsBegin() >= end.utf16charsBegin()
317                         || commentToken.utf16charsEnd() <= begin.utf16charsBegin()) {
318                     continue;
319                 }
320                 const QString comment = stringOf(commentToken);
321                 const QRegularExpressionMatch match = uriAnnotation.match(comment);
322                 if (match.hasMatch()) {
323                     packageName = match.captured(1);
324                     break;
325                 }
326             }
327         }
328         if (packageName.isEmpty()) {
329             packageName = QmlJS::CppQmlTypes::defaultPackage;
330             int line, column;
331             translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
332             _messages += Document::DiagnosticMessage(
333                         Document::DiagnosticMessage::Warning,
334                         _doc->fileName(),
335                         line, column,
336                         QmlJS::FindExportedCppTypes::tr(
337                             "The module URI cannot be determined by static analysis. The type will be available\n"
338                             "globally in the QML editor. You can add a \"// @uri My.Module.Uri\" annotation to let\n"
339                             "the QML editor know about a likely URI."));
340         }
341 
342         // version arguments must be integer literals
343         const NumericLiteral *majorLit = nullptr;
344         const NumericLiteral *minorLit = nullptr;
345         if (NumericLiteralAST *majorAst = majorVersionExp->asNumericLiteral())
346             majorLit = translationUnit()->numericLiteral(majorAst->literal_token);
347         if (NumericLiteralAST *minorAst = minorVersionExp->asNumericLiteral())
348             minorLit = translationUnit()->numericLiteral(minorAst->literal_token);
349 
350         // build the descriptor
351         ExportedQmlType exportedType;
352         exportedType.isSingleton = registrationFunction == QmlRegisterSingletonInstance
353                 || registrationFunction == QmlRegisterSingletonTypeCallback1
354                 || registrationFunction == QmlRegisterSingletonTypeCallback2
355                 || registrationFunction == QmlRegisterSingletonTypeUrl;
356         exportedType.isCreatable = !exportedType.isSingleton
357                 && registrationFunction != QmlRegisterUncreatableType
358                 && registrationFunction != QmlRegisterUncreatableMetaObject;
359         exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size());
360         exportedType.packageName = packageName;
361         if (majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) {
362             exportedType.version = LanguageUtils::ComponentVersion(
363                         QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt(),
364                         QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt());
365         }
366 
367         // we want to do lookup later, so also store the surrounding scope
368         int line, column;
369         translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
370         exportedType.scope = _doc->scopeAt(line, column);
371 
372         if (typeId){
373             // add the expression
374             const Token begin = translationUnit()->tokenAt(typeId->firstToken());
375             const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1);
376             exportedType.typeExpression = QString::fromUtf8(
377                         _doc->utf8Source().mid(begin.bytesBegin(), last.bytesEnd() - begin.bytesBegin()));
378         } else if (registrationFunction == QmlRegisterSingletonTypeCallback2) {
379             // we cannot really do much with generated QJSValue values
380         } else if (registrationFunction == QmlRegisterSingletonTypeUrl
381                    || registrationFunction == QmlRegisterType5) {
382             // try to recover QUrl("literal")
383             if (ast->expression_list && ast->expression_list->value) {
384                 ExpressionAST *urlAst = skipStringCall(skipQUrl(ast->expression_list->value, translationUnit()));
385                 if (StringLiteralAST *uriAst = urlAst->asStringLiteral()) {
386                     const StringLiteral *urlLit = translationUnit()->stringLiteral(uriAst->literal_token);
387                     exportedType.url = QString::fromUtf8(urlLit->chars(), urlLit->size());
388                 }
389             }
390         } else if (registrationFunction == QmlRegisterUncreatableMetaObject) {
391             // Anything to do here?
392         } else {
393             qCWarning(QmlJS::qmljsLog()) << "missing template type for registrationFunction " << registrationFunction;
394         }
395         _exportedTypes += exportedType;
396 
397         return true;
398     }
399 
callName(ExpressionAST * exp)400     static NameAST *callName(ExpressionAST *exp)
401     {
402         if (IdExpressionAST *idExp = exp->asIdExpression())
403             return idExp->name;
404         if (MemberAccessAST *memberExp = exp->asMemberAccess())
405             return memberExp->member_name;
406         return nullptr;
407     }
408 
skipQVariant(ExpressionAST * ast,TranslationUnit * translationUnit)409     static ExpressionAST *skipQVariant(ExpressionAST *ast, TranslationUnit *translationUnit)
410     {
411         CallAST *call = ast->asCall();
412         if (!call)
413             return ast;
414         if (!call->expression_list
415                 || !call->expression_list->value
416                 || call->expression_list->next)
417             return ast;
418 
419         IdExpressionAST *idExp = call->base_expression->asIdExpression();
420         if (!idExp || !idExp->name)
421             return ast;
422 
423         // QVariant(foo) -> foo
424         if (SimpleNameAST *simpleName = idExp->name->asSimpleName()) {
425             const Identifier *id = translationUnit->identifier(simpleName->identifier_token);
426             if (!id)
427                 return ast;
428             if (QString::fromUtf8(id->chars(), id->size()) != QLatin1String("QVariant"))
429                 return ast;
430             return call->expression_list->value;
431         // QVariant::fromValue(foo) -> foo
432         } else if (QualifiedNameAST *q = idExp->name->asQualifiedName()) {
433             SimpleNameAST *simpleRhsName = q->unqualified_name->asSimpleName();
434             if (!simpleRhsName
435                     || !q->nested_name_specifier_list
436                     || !q->nested_name_specifier_list->value
437                     || q->nested_name_specifier_list->next)
438                 return ast;
439             const Identifier *rhsId = translationUnit->identifier(simpleRhsName->identifier_token);
440             if (!rhsId)
441                 return ast;
442             if (QString::fromUtf8(rhsId->chars(), rhsId->size()) != QLatin1String("fromValue"))
443                 return ast;
444             NestedNameSpecifierAST *nested = q->nested_name_specifier_list->value;
445             SimpleNameAST *simpleLhsName = nested->class_or_namespace_name->asSimpleName();
446             if (!simpleLhsName)
447                 return ast;
448             const Identifier *lhsId = translationUnit->identifier(simpleLhsName->identifier_token);
449             if (!lhsId)
450                 return ast;
451             if (QString::fromUtf8(lhsId->chars(), lhsId->size()) != QLatin1String("QVariant"))
452                 return ast;
453             return call->expression_list->value;
454         }
455 
456         return ast;
457     }
458 
skipQUrl(ExpressionAST * ast,TranslationUnit * translationUnit)459     static ExpressionAST *skipQUrl(ExpressionAST *ast, TranslationUnit *translationUnit)
460     {
461         CallAST *call = ast->asCall();
462         if (!call)
463             return ast;
464         if (!call->expression_list
465                 || !call->expression_list->value
466                 || call->expression_list->next)
467             return ast;
468 
469         IdExpressionAST *idExp = call->base_expression->asIdExpression();
470         if (!idExp || !idExp->name)
471             return ast;
472 
473         // QUrl(foo) -> foo
474         if (SimpleNameAST *simpleName = idExp->name->asSimpleName()) {
475             const Identifier *id = translationUnit->identifier(simpleName->identifier_token);
476             if (!id)
477                 return ast;
478             if (QByteArray(id->chars(), id->size()) == "QUrl")
479                 return call->expression_list->value;
480         }
481 
482         return ast;
483     }
484 
checkForSetContextProperty(CallAST * ast)485     bool checkForSetContextProperty(CallAST *ast)
486     {
487         // check whether ast->base_expression has the 'setContextProperty' name
488         NameAST *callNameAst = callName(ast->base_expression);
489         if (!callNameAst)
490             return false;
491         SimpleNameAST *simpleNameAst = callNameAst->asSimpleName();
492         if (!simpleNameAst || !simpleNameAst->identifier_token)
493             return false;
494         const Identifier *nameIdentifier = translationUnit()->identifier(simpleNameAst->identifier_token);
495         if (!nameIdentifier)
496             return false;
497         const QString callName = QString::fromUtf8(nameIdentifier->chars(), nameIdentifier->size());
498         if (callName != QLatin1String("setContextProperty"))
499             return false;
500 
501         // must have two arguments
502         if (!ast->expression_list
503                 || !ast->expression_list->value || !ast->expression_list->next
504                 || !ast->expression_list->next->value || ast->expression_list->next->next)
505             return false;
506 
507         // first argument must be a string literal
508         const StringLiteral *nameLit = nullptr;
509         if (StringLiteralAST *nameAst = skipStringCall(ast->expression_list->value)->asStringLiteral())
510             nameLit = translationUnit()->stringLiteral(nameAst->literal_token);
511         if (!nameLit) {
512             int line, column;
513             translationUnit()->getTokenStartPosition(ast->expression_list->value->firstToken(), &line, &column);
514             _messages += Document::DiagnosticMessage(
515                         Document::DiagnosticMessage::Warning,
516                         _doc->fileName(),
517                         line, column,
518                         QmlJS::FindExportedCppTypes::tr(
519                             "must be a string literal to be available in the QML editor"));
520             return false;
521         }
522 
523         ContextProperty contextProperty;
524         contextProperty.name = QString::fromUtf8(nameLit->chars(), nameLit->size());
525         contextProperty.expression = stringOf(skipQVariant(ast->expression_list->next->value, translationUnit()));
526         // we want to do lookup later, so also store the line and column of the target scope
527         translationUnit()->getTokenStartPosition(ast->firstToken(),
528                                                  &contextProperty.line,
529                                                  &contextProperty.column);
530 
531         _contextProperties += contextProperty;
532 
533         return true;
534     }
535 
536 private:
stringOf(CPlusPlus::AST * ast)537     QString stringOf(CPlusPlus::AST *ast)
538     {
539         return stringOf(ast->firstToken(), ast->lastToken() - 1);
540     }
541 
stringOf(int first,int last)542     QString stringOf(int first, int last)
543     {
544         const Token firstToken = translationUnit()->tokenAt(first);
545         const Token lastToken = translationUnit()->tokenAt(last);
546         return QString::fromUtf8(
547             _doc->utf8Source().mid(firstToken.bytesBegin(),
548                                    lastToken.bytesEnd() - firstToken.bytesBegin()));
549     }
550 
stringOf(const Token & token)551     QString stringOf(const Token &token)
552     {
553         return QString::fromUtf8(_doc->utf8Source().mid(token.bytesBegin(), token.bytes()));
554     }
555 
skipStringCall(ExpressionAST * exp)556     ExpressionAST *skipStringCall(ExpressionAST *exp)
557     {
558         if (!exp || !exp->asCall())
559             return exp;
560 
561         IdExpressionAST *callName = _builder.IdExpression();
562         CallAST *call = _builder.Call(callName);
563         if (!exp->match(call, &_matcher))
564             return exp;
565 
566         const QString name = stringOf(callName);
567         if (name != QLatin1String("QLatin1String")
568                 && name != QLatin1String("QString"))
569             return exp;
570 
571         if (!call->expression_list || call->expression_list->next)
572             return exp;
573 
574         return call->expression_list->value;
575     }
576 
nameOfUriAssert(StatementAST * stmt,const QString & uriName)577     QString nameOfUriAssert(StatementAST *stmt, const QString &uriName)
578     {
579         QString null;
580 
581         IdExpressionAST *outerCallName = _builder.IdExpression();
582         BinaryExpressionAST *binary = _builder.BinaryExpression();
583         // assert(... == ...);
584         ExpressionStatementAST *pattern = _builder.ExpressionStatement(
585                     _builder.Call(outerCallName, _builder.ExpressionList(
586                                      binary)));
587 
588         if (!stmt->match(pattern, &_matcher)) {
589             outerCallName = _builder.IdExpression();
590             binary = _builder.BinaryExpression();
591             // the expansion of Q_ASSERT(...),
592             // ((!(... == ...)) ? qt_assert(...) : ...);
593             pattern = _builder.ExpressionStatement(
594                         _builder.NestedExpression(
595                             _builder.ConditionalExpression(
596                                 _builder.NestedExpression(
597                                     _builder.UnaryExpression(
598                                         _builder.NestedExpression(
599                                             binary))),
600                                 _builder.Call(outerCallName))));
601 
602             if (!stmt->match(pattern, &_matcher))
603                 return null;
604         }
605 
606         const QString outerCall = stringOf(outerCallName);
607         if (outerCall != QLatin1String("qt_assert")
608                 && outerCall != QLatin1String("assert")
609                 && outerCall != QLatin1String("Q_ASSERT"))
610             return null;
611 
612         if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL)
613             return null;
614 
615         ExpressionAST *lhsExp = skipStringCall(binary->left_expression);
616         ExpressionAST *rhsExp = skipStringCall(binary->right_expression);
617         if (!lhsExp || !rhsExp)
618             return null;
619 
620         StringLiteralAST *uriString = lhsExp->asStringLiteral();
621         IdExpressionAST *uriArgName = lhsExp->asIdExpression();
622         if (!uriString)
623             uriString = rhsExp->asStringLiteral();
624         if (!uriArgName)
625             uriArgName = rhsExp->asIdExpression();
626         if (!uriString || !uriArgName)
627             return null;
628 
629         if (stringOf(uriArgName) != uriName)
630             return null;
631 
632         const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token);
633         return QString::fromUtf8(packageLit->chars(), packageLit->size());
634     }
635 };
636 
stripPointerAndReference(const FullySpecifiedType & type)637 static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type)
638 {
639     Type *t = type.type();
640     while (t) {
641         if (PointerType *ptr = t->asPointerType())
642             t = ptr->elementType().type();
643         else if (ReferenceType *ref = t->asReferenceType())
644             t = ref->elementType().type();
645         else
646             break;
647     }
648     return FullySpecifiedType(t);
649 }
650 
toQmlType(const FullySpecifiedType & type)651 static QString toQmlType(const FullySpecifiedType &type)
652 {
653     Overview overview;
654     QString result = overview.prettyType(stripPointerAndReference(type));
655     if (result == QLatin1String("QString"))
656         result = QLatin1String("string");
657     return result;
658 }
659 
lookupClass(const QString & expression,Scope * scope,TypeOfExpression & typeOf)660 static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf)
661 {
662     QList<LookupItem> results = typeOf(expression.toUtf8(), scope);
663     Class *klass = nullptr;
664     foreach (const LookupItem &item, results) {
665         if (item.declaration()) {
666             klass = item.declaration()->asClass();
667             if (klass)
668                 return klass;
669         }
670     }
671     return nullptr;
672 }
673 
buildFakeMetaObject(Class * klass,QHash<Class *,LanguageUtils::FakeMetaObject::Ptr> * fakeMetaObjects,TypeOfExpression & typeOf)674 static LanguageUtils::FakeMetaObject::Ptr buildFakeMetaObject(
675         Class *klass,
676         QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects,
677         TypeOfExpression &typeOf)
678 {
679     using namespace LanguageUtils;
680 
681     if (FakeMetaObject::Ptr fmo = fakeMetaObjects->value(klass))
682         return fmo;
683 
684     FakeMetaObject::Ptr fmo(new FakeMetaObject);
685     if (!klass)
686         return fmo;
687     fakeMetaObjects->insert(klass, fmo);
688 
689     Overview namePrinter;
690 
691     fmo->setClassName(namePrinter.prettyName(klass->name()));
692     // add the no-package export, so the cpp name can be used in properties
693     fmo->addExport(fmo->className(), QmlJS::CppQmlTypes::cppPackage, ComponentVersion());
694 
695     for (int i = 0; i < klass->memberCount(); ++i) {
696         Symbol *member = klass->memberAt(i);
697         if (!member->name())
698             continue;
699         if (Function *func = member->type()->asFunctionType()) {
700             if (!func->isSlot() && !func->isInvokable() && !func->isSignal())
701                 continue;
702             FakeMetaMethod method(namePrinter.prettyName(func->name()), toQmlType(func->returnType()));
703             if (func->isSignal())
704                 method.setMethodType(FakeMetaMethod::Signal);
705             else
706                 method.setMethodType(FakeMetaMethod::Slot);
707             for (int a = 0, argc = func->argumentCount(); a < argc; ++a) {
708                 Symbol *arg = func->argumentAt(a);
709                 QString name;
710                 if (arg->name())
711                     name = namePrinter.prettyName(arg->name());
712                 method.addParameter(name, toQmlType(arg->type()));
713             }
714             fmo->addMethod(method);
715         }
716         if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) {
717             const FullySpecifiedType &type = propDecl->type();
718             const bool isList = false; // ### fixme
719             const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction;
720             const bool isPointer = type.type() && type.type()->isPointerType();
721             const int revision = 0; // ### fixme
722             FakeMetaProperty property(
723                         namePrinter.prettyName(propDecl->name()),
724                         toQmlType(type),
725                         isList, isWritable, isPointer,
726                         revision);
727             fmo->addProperty(property);
728         }
729         if (QtEnum *qtEnum = member->asQtEnum()) {
730             // find the matching enum
731             Enum *e = nullptr;
732             QList<LookupItem> result = typeOf(namePrinter.prettyName(qtEnum->name()).toUtf8(), klass);
733             foreach (const LookupItem &item, result) {
734                 if (item.declaration()) {
735                     e = item.declaration()->asEnum();
736                     if (e)
737                         break;
738                 }
739             }
740             if (!e)
741                 continue;
742 
743             FakeMetaEnum metaEnum(namePrinter.prettyName(e->name()));
744             for (int j = 0; j < e->memberCount(); ++j) {
745                 Symbol *enumMember = e->memberAt(j);
746                 if (!enumMember->name())
747                     continue;
748                 metaEnum.addKey(namePrinter.prettyName(enumMember->name()));
749             }
750             fmo->addEnum(metaEnum);
751         }
752     }
753 
754     // only single inheritance is supported
755     if (klass->baseClassCount() > 0) {
756         BaseClass *base = klass->baseClassAt(0);
757         if (!base->name())
758             return fmo;
759         const QString baseClassName = namePrinter.prettyName(base->name());
760         fmo->setSuperclassName(baseClassName);
761 
762         Class *baseClass = lookupClass(baseClassName, klass, typeOf);
763         if (!baseClass)
764             return fmo;
765         buildFakeMetaObject(baseClass, fakeMetaObjects, typeOf);
766     }
767 
768     return fmo;
769 }
770 
buildExportedQmlObjects(TypeOfExpression & typeOf,const QList<ExportedQmlType> & cppExports,QHash<Class *,LanguageUtils::FakeMetaObject::Ptr> * fakeMetaObjects,QList<LanguageUtils::FakeMetaObject::Ptr> * extraFakeMetaObjects)771 static void buildExportedQmlObjects(
772         TypeOfExpression &typeOf,
773         const QList<ExportedQmlType> &cppExports,
774         QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects,
775         QList<LanguageUtils::FakeMetaObject::Ptr> *extraFakeMetaObjects)
776 {
777     using namespace LanguageUtils;
778 
779     if (cppExports.isEmpty())
780         return;
781 
782     foreach (const ExportedQmlType &exportedType, cppExports) {
783         Class *klass = nullptr;
784         if (!exportedType.typeExpression.isEmpty())
785             klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf);
786         // TODO: something smarter with exportedType.url
787         // accepts a null klass
788         FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf);
789         fmo->addExport(exportedType.typeName,
790                        exportedType.packageName,
791                        exportedType.version);
792         fmo->setIsCreatable(exportedType.isCreatable);
793         fmo->setIsSingleton(exportedType.isSingleton);
794         if (!klass) {
795             fmo->setClassName(QLatin1String("__autogen__") + exportedType.typeName);
796             extraFakeMetaObjects->append(fmo);
797         }
798     }
799 }
800 
buildContextProperties(const Document::Ptr & doc,TypeOfExpression & typeOf,const QList<ContextProperty> & contextPropertyDescriptions,QHash<Class *,LanguageUtils::FakeMetaObject::Ptr> * fakeMetaObjects,QHash<QString,QString> * contextProperties)801 static void buildContextProperties(
802         const Document::Ptr &doc,
803         TypeOfExpression &typeOf,
804         const QList<ContextProperty> &contextPropertyDescriptions,
805         QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects,
806         QHash<QString, QString> *contextProperties)
807 {
808     using namespace LanguageUtils;
809 
810     foreach (const ContextProperty &property, contextPropertyDescriptions) {
811         Scope *scope = doc->scopeAt(property.line, property.column);
812         QList<LookupItem> results = typeOf(property.expression.toUtf8(), scope);
813         QString typeName;
814         if (!results.isEmpty()) {
815             LookupItem result = results.first();
816             FullySpecifiedType simpleType = stripPointerAndReference(result.type());
817             if (NamedType *namedType = simpleType.type()->asNamedType()) {
818                 Scope *typeScope = result.scope();
819                 if (!typeScope)
820                     typeScope = scope; // incorrect but may be an ok fallback
821                 ClassOrNamespace *binding = typeOf.context().lookupType(namedType->name(), typeScope);
822                 if (binding && !binding->symbols().isEmpty()) {
823                     // find the best 'Class' symbol
824                     for (int i = binding->symbols().size() - 1; i >= 0; --i) {
825                         if (Class *klass = binding->symbols().at(i)->asClass()) {
826                             FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf);
827                             typeName = fmo->className();
828                             break;
829                         }
830                     }
831                 }
832             }
833         }
834 
835         contextProperties->insert(property.name, typeName);
836     }
837 }
838 
839 } // anonymous namespace
840 
841 namespace QmlJS {
842 
FindExportedCppTypes(const CPlusPlus::Snapshot & snapshot)843 FindExportedCppTypes::FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot)
844     : m_snapshot(snapshot)
845 {
846 }
847 
operator ()(const CPlusPlus::Document::Ptr & document)848 QStringList FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document)
849 {
850     QTC_ASSERT(!document.isNull(), return QStringList());
851 
852     m_contextProperties.clear();
853     m_exportedTypes.clear();
854     QStringList fileNames;
855 
856     // this check only guards against some input errors, if document's source and AST has not
857     // been guarded properly the source and AST may still become empty/null while this function is running
858     if (document->utf8Source().isEmpty()
859             || !document->translationUnit()->ast())
860         return fileNames;
861 
862     FindExportsVisitor finder(document);
863     finder();
864     static const QString kindKey = QLatin1String("QmlJSTools.ExportedQmlTypesDiagnostic");
865     CppModelManagerBase::trySetExtraDiagnostics(document->fileName(), kindKey,
866                                                 finder.messages());
867 
868     // if nothing was found, done
869     const QList<ContextProperty> contextPropertyDescriptions = finder.contextProperties();
870     const QList<ExportedQmlType> exports = finder.exportedTypes();
871     if (exports.isEmpty() && contextPropertyDescriptions.isEmpty())
872         return fileNames;
873 
874     // context properties need lookup inside function scope, and thus require a full check
875     CPlusPlus::Document::Ptr localDoc = document;
876     if (document->checkMode() != CPlusPlus::Document::FullCheck && !contextPropertyDescriptions.isEmpty()) {
877         localDoc = m_snapshot.documentFromSource(document->utf8Source(), document->fileName());
878         localDoc->check();
879     }
880 
881     // create a single type of expression (and bindings) for the document
882     TypeOfExpression typeOf;
883     typeOf.init(localDoc, m_snapshot);
884     QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> fakeMetaObjects;
885     QList<LanguageUtils::FakeMetaObject::Ptr> extraFakeMetaObjects;
886 
887     // generate the exports from qmlRegisterType
888     buildExportedQmlObjects(typeOf, exports, &fakeMetaObjects, &extraFakeMetaObjects);
889 
890     // add the types from the context properties and create a name->cppname map
891     // also expose types where necessary
892     buildContextProperties(localDoc, typeOf, contextPropertyDescriptions,
893                            &fakeMetaObjects, &m_contextProperties);
894 
895     // convert to list of FakeMetaObject::ConstPtr
896     m_exportedTypes.reserve(fakeMetaObjects.size() + extraFakeMetaObjects.size());
897     fileNames.reserve(fakeMetaObjects.size());
898     for (auto it = fakeMetaObjects.constBegin(), end = fakeMetaObjects.constEnd(); it != end;
899          ++it) {
900         it.value()->updateFingerprint();
901         m_exportedTypes += it.value();
902         fileNames += QLatin1String(it.key()->fileName());
903     }
904     foreach (const LanguageUtils::FakeMetaObject::Ptr &fmo, extraFakeMetaObjects) {
905         fmo->updateFingerprint();
906         m_exportedTypes += fmo;
907     }
908 
909     return fileNames;
910 }
911 
exportedTypes() const912 QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::exportedTypes() const
913 {
914     return m_exportedTypes;
915 }
916 
contextProperties() const917 QHash<QString, QString> FindExportedCppTypes::contextProperties() const
918 {
919     return m_contextProperties;
920 }
921 
maybeExportsTypes(const CPlusPlus::Document::Ptr & document)922 bool FindExportedCppTypes::maybeExportsTypes(const CPlusPlus::Document::Ptr &document)
923 {
924     if (!document->control())
925         return false;
926     const QByteArray tokens[] = {
927         "qmlRegisterSingletonInstance",
928         "qmlRegisterSingletonType",
929         "qmlRegisterType",
930         "qmlRegisterUncreatableType",
931         "setContextProperty"
932         "qmlRegisterUncreatableMetaObject",
933     };
934     for (const QByteArray &token : tokens) {
935         if (document->control()->findIdentifier(token.constData(), token.size())) {
936             return true;
937         }
938     }
939     return false;
940 }
941 
942 } // namespace QmlJS
943