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